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!