Why is `mut` linted here?


#1

In the following code (which is contrived, but reproduces a real-world reasonable scenario, I get the lint warning: variable does not need to be mutable for the bar function. And sure enough, I can remove mut and the code still works. And yet the bar function mutates self through the set_bar function.

So what’s up?

struct Foo {
	bar: i32
}

impl Foo {
	fn set_bar (mut self, bar: i32) -> Self {
		self.bar = bar;
		self
	}

	fn bar(mut self, bar: i32) -> Self {
		self.set_bar(bar)
	}
}

#2

Here’s my full running example:

#[derive(Debug)]
struct Foo {
    bar: i32
}

impl Foo {
    fn set_bar(mut self, bar: i32) -> Self {
        self.bar = bar;
        self
    }

    fn bar(self, bar: i32) -> Self {
        self.set_bar(bar)
    }
}

fn main() {
    let foo = Foo { bar: 13 };

    let foo2 = foo.set_bar(42);
    println!("{:?}", foo2);

    let foo3 = foo2.bar(54);
    println!("{:?}", foo3);
}

The reason that this isn’t needed is that you’re taking ownership. You’re re-assigning self, which is what mut means here. You can see this reflected in the need for foo2 and foo3 in main(), we’ve moved foo and so cannot use it.

I think this is the code you wanted to write:

#[derive(Debug)]
struct Foo {
    bar: i32
}

impl Foo {
    fn set_bar(&mut self, bar: i32) {
        self.bar = bar;
    }

    fn bar(&mut self, bar: i32) {
        self.set_bar(bar)
    }
}

fn main() {
    let mut foo = Foo { bar: 13 };

    foo.set_bar(42);
    println!("{:?}", foo);

    foo.bar(54);
    println!("{:?}", foo);
}

By taking &mut self, you’re actually doing mutation of what the binding is pointing to, rather than changing the binding.


#3

The difference hit me when I tried the following:

let f = Foo{bar:5};
let _ = f.bar(20);

And hit a moved value error. Which means that my intuition for what Rust is doing is still very immature.

The reason to return `self`` is to allow fluid method chaining. I think I now understand that that’s underlying implemented as a sequence of moves of ownership?

Still so much to learn.


#4

Exactly! By constantly returning self you give up ownership of self to the caller again, so it can go on calling new methods. It’s a common approach. Just don’t forget to reassign the result of a call in your call site to avoid losing ownership. The drawback is you make a copy of self with each of such call and return, so if your type is large enough, you better pass a reference (&mut self if you need to mutate your state).