self in fn Definitions
In many languages, this is a magical language keyword. You have a method on a
type like:
class User {
int id;
/* ... */
int getID() {
return this.id;
}
}
Where did this come from? Of course, it’s automatically provided by the
language. In practically all cases, instance methods are similar to free functions
with the current instance being passed as the first parameter.
In other words, the compiler actually generates code like:
class User {
int id;
/* ... */
static int getID(User this) {
return this.id;
}
}
So, when you originally called:
User user = new User();
user.getId();
The compiler was really generating:
User user = new User();
User.getId(user);
There are a few languages, notably Python, which do require a this (usually
named self in Python) parameter in instance method declarations, but most
languages do not require it. The omission of forcing every instance function to
have a this parameter could be considered syntactical sugar.
Rust
In Rust, the self parameter is not hidden away and is required. It is an
important part of the function declaration because how self is declared
indicates if the value is borrowed, mutably borrowed, or owned.
#![allow(unused)] fn main() { struct Database {} struct User { id: i64, } impl User { fn get_id(&self) -> i64 { self.id } fn set_id(&mut self, id: i64) { self.id = id; } fn delete(self, db: Database) { todo!() } } }
The self parameter’s type is indicated by the impl block definition.
So the code which is generated is closer to free functions like:
#![allow(unused)] fn main() { struct User { id: i64, } fn get_id(user: &User) -> i64 { user.id } }
Furthermore, the impl block is important for declaring other constraints on
the functions. If you had a wrapper type like:
#![allow(unused)] fn main() { struct Wrapper<T> { inner: T } impl<T> Wrapper<T> { fn to_inner(self) -> T { self.inner } } }
The generated code is similar to:
#![allow(unused)] fn main() { struct Wrapper<T> { inner: T } fn to_inner<T>(wrapper: Wrapper<T>) -> T { wrapper.inner } }
Declaring methods on a type is not too magical. In the end, methods are merely
conveniences and standalone free functions could be used. In Rust, additional
information is conveyed with the self parameter related to the borrow rules.