Files
100-exercises-to-learn-rust/book/src/03_ticket_v1/07_setters.md
LukeMathWalker 4401743807 Formatter
2024-05-24 18:16:20 +02:00

3.1 KiB
Raw Blame History

Mutable references

Your accessor methods should look like this now:

impl Ticket {
    pub fn title(&self) -> &String {
        &self.title
    }

    pub fn description(&self) -> &String {
        &self.description
    }

    pub fn status(&self) -> &String {
        &self.status
    }
}

A sprinkle of & here and there did the trick!
We now have a way to access the fields of a Ticket instance without consuming it in the process. Lets see how we can enhance our Ticket struct with setter methods next.

Setters

Setter methods allow users to change the values of Tickets private fields while making sure that its invariants are respected (i.e. you cant set a Tickets title to an empty string).

There are two common ways to implement setters in Rust:

  • Taking self as input.
  • Taking &mut self as input.

Taking self as input

The first approach looks like this:

impl Ticket {
    pub fn set_title(mut self, new_title: String) -> Self {
        // Validate the new title [...]
        self.title = new_title;
        self
    }
}

It takes ownership of self, changes the title, and returns the modified Ticket instance.
This is how youd use it:

let ticket = Ticket::new("Title".into(), "Description".into(), "To-Do".into());
let ticket = ticket.set_title("New title".into());

Since set_title takes ownership of self (i.e. it consumes it), we need to reassign the result to a variable. In the example above we take advantage of variable shadowing to reuse the same variable name: when you declare a new variable with the same name as an existing one, the new variable shadows the old one. This is a common pattern in Rust code.

self-setters work quite nicely when you need to change multiple fields at once: you can chain multiple calls together!

let ticket = ticket
    .set_title("New title".into())
    .set_description("New description".into())
    .set_status("In Progress".into());

Taking &mut self as input

The second approach to setters, using &mut self, looks like this instead:

impl Ticket {
    pub fn set_title(&mut self, new_title: String) {
        // Validate the new title [...]
        
        self.title = new_title;
    }
}

This time the method takes a mutable reference to self as input, changes the title, and thats it. Nothing is returned.

Youd use it like this:

let mut ticket = Ticket::new("Title".into(), "Description".into(), "To-Do".into());
ticket.set_title("New title".into());

// Use the modified ticket

Ownership stays with the caller, so the original ticket variable is still valid. We dont need to reassign the result. We need to mark ticket as mutable though, because were taking a mutable reference to it.

&mut-setters have a downside: you cant chain multiple calls together. Since they dont return the modified Ticket instance, you cant call another setter on the result of the first one. You have to call each setter separately:

ticket.set_title("New title".into());
ticket.set_description("New description".into());
ticket.set_status("In Progress".into());