Attaching code to types
If you want to add behaviour or other associated code to your types, you can do so by writing an impl
block on that type. Such a block can contain associated constants, types and functions. There can also be multiple impl
blocks for the same type, for example in different files.
Methods that are called on an object instance are written as a regular function, but take some form of self
as their first parameter, meaning they need an object instance to be called. In contrast, functions without a self
parameter can only be called on the type, not on an instance.
Object methods can also be called like a normal associated function, but need an object instance for their first parameter in that case, similar to how C++ handles things.
Additionally to the self
keyword, which translates to this
in many other languages, there is also a Self
keyword with a capitalized S. This doesn't refer to an object instance, but rather to the type which is implemented in the current impl
block. It can be used instead of the explicit type to make the relation more clear.
For example, a constructor typically is a stock standard function that does not use a self
parameter, but returns a Self
object.
In contrast, self
can't be replaced by anything else and fields or methods of the objects can only be accessed explicitly through it. For people who don't have all fields and method names in their head, this makes it very easy to identify those elements. It also helps to discern them from external constants or functions and local variables.
struct Rectangle { width: usize, height: usize, } impl Rectangle { const MAX_HEIGHT: usize = 42; // Notice the capitalized 'Self' as type const EXAMPLE: Self = Self { width: 2, height: 1, }; // Default constructor, no special case in the language pub fn new() -> Self { return Self::EXAMPLE; } pub fn associated_function() -> usize { return Self::MAX_HEIGHT; } // Non-capitalized 'self' as object instance pub fn object_method(self) -> usize { return self.width; } } fn example() { let object: Rectangle = Rectangle::new(); let another_object: Rectangle = Rectangle::new(); println!("{}", Rectangle::associated_function()); println!("{}", object.object_method()); // Calling a method as associated function works, too! println!("{}", Rectangle::object_method(another_object)); } fn main() { example(); }
Now traits make implementations much more flexible still, but more on that, you guessed it, in its own chapter.