All about self
In this chapter we'll explore the self keyword and its uses.
In previous chapters we have seen some examples of methods that take &self as a parameter. We've also seen
that self can be used to call methods on a type.
For example:
struct Circle { radius: f64, } impl Circle { fn area(&self) -> f64 { std::f64::consts::PI * self.radius.powi(2) } } fn main() { let circle = Circle { radius: 10.0 }; println!("Area: {}", circle.area()); }
In this example, self is used to call the area method on the circle instance.
Why do we need the & in fn area(&self)?
The & is a reference to the Circle instance. This is because we don't want to take ownership of the Circle
instance when calling the area method. We only want to borrow it.
So... Are there other ways to use self? Yes, there are. Let's explore them.
self in methods
In the previous example, we used &self to borrow the Circle instance. We can also use self to take ownership of
the instance. This is useful when we want to 'consume' the instance and return a new instance in its place.
This is commonly used to implement type conversion methods.
For example:
struct Circle { radius: f64, } struct BigCircle { radius: f64, } impl Circle { fn new(radius: f64) -> Circle { Circle { radius } } fn into_big_circle(self) -> BigCircle { BigCircle { radius: self.radius * 2.0 } } } fn main() { let big_circle = Circle::new(2.0).into_big_circle(); println!("Radius: {}", big_circle.radius); }
In this example, the into_big_circle method takes ownership of the Circle instance and returns a new BigCircle
instance with the new radius.
mut self in methods
We can also use mut self to take a mutable reference to the instance. This is useful when we want to modify the
instance in place. This is commonly used to implement 'builder' patterns.
For example:
struct Circle { radius: f64, } impl Circle { fn new() -> Circle { Circle { radius: 1.0 } } fn with_radius(mut self, radius: f64) -> Circle { self.radius = radius; self } } fn main() { let circle = Circle::new().with_radius(10.0); println!("Radius: {}", circle.radius); }
In this example, the with_radius method takes a mutable ownership of the Circle instance and modifies it in place.
&mut self in methods
We can also use &mut self to take a mutable reference to the instance. This is useful when we want to modify the
instance in place, but don't want to take ownership of it.
For example:
struct Circle { radius: f64, } impl Circle { fn new() -> Circle { Circle { radius: 1.0 } } fn set_radius(&mut self, radius: f64) { self.radius = radius; } } fn main() { let mut circle = Circle::new(); circle.set_radius(10.0); println!("Radius: {}", circle.radius); }
Self
The Self keyword is used to refer to the type of the current instance. It is useful when we want to return a new
instance of the same type from a method.
For example:
struct Circle { radius: f64, } impl Circle { fn new(radius: f64) -> Self { Circle { radius } } fn with_radius(mut self, radius: f64) -> Self { self.radius = radius; self } } fn main() { let circle = Circle::new(1.0).with_radius(10.0); println!("Radius: {}", circle.radius); }
Exercises
This might be a good time to take a break and do some exercises. Have a look at the exercises in the Exercises section. Complete the exercise that is related to this session.
See you next time!