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!