Should setters mutate, move, or clone the value?

The guideline (which claims to be approved by RFC #344) says that the setter should have the format set_foo(&self, val: T) (or set_foo(&self, val: &T), but I’d ignore this issue for the rest of this post). However, a method with such a signature could not function as a setter at all.

So what is the right signature?

  • Mutates the value: set_foo(&mut self, val: T) (Modifies the value in-place)
  • Moves the value: set_foo(self, val: T) -> SomeType (Consumes the value, modifies it in place, and returns it)
  • Clones the value: set_foo(&self, val: T) -> SomeType (Do a functional update on a value and return the new value)
1 Like

Rust datastructures do not act functionally, they take &mut self instead of &self when they need to mutate.

I would say to use set_foo(&mut self, val: T) for a conventional setter. If your API would like to follow a builder pattern API however, you could do something like your second example: foo(self, val: T) -> SomeType

2 Likes

That was what I initially thought. However, if you have a setter that moves, you can use it like a setter that mutates. On the other hand, if you have a setter that mutates, I don’t think you can use it like a setter that moves. Correct me if I am wrong.

However, having setters that move seem to be slightly more counter-intuitive than setters that mutates, though.

Code example:

struct Foo {
    val: int,
}
impl Foo {
    fn inc(mut self) -> Foo { self.val += 1; self }
}

fn main() {
    let mut x = Foo{val: 1,};
    x = x.inc();
    let val = x.val;
    print!("{}" , val);
}

Playground link

I initially thought that this code would not compile, but the borrow checker is apparently OK with this. Also note that x does not have to be mut to be able to call x.inc(), since it is mut self, not mut& self. (In that case, it would be impossible to reassign the result to x, though.)

rust-modifier uses traits so that you define how a type can be used to modify a struct and then you automatically get both types of setters.

using &mut self for setters should be you default choice. Through using just self might be useful e.g. in a builder pattern. Many builders out there (in other languages) do mutate the object/data and the return itself, so using fn set_x(&mut self, v: T ) -> Self might be preferable.

That’s only reasonably possible for Copyable types though.

Even for Copy or Clone types, I don’t think that this is a good idea. This requires unnecessary copying or cloning, which degrades the performance. This might be OK for languages that don’t strive for performance, though.

Oh, that’s true returning Self might not be the best idea. (I was to deep in JavaWorld where returning Self means returning a reference/pointer, so the rust equivalent would be fn set_x<'a>(&'a mut self, v: T) -> &'a mut Self This would allow both single and chained, builder like setting of states, through I’m not sure if it has performance penalties)

Also note that the solution posted above by me has not only performance penalties but might also introduce bugs/inconsistence when single and chained calling style is mixed

Coming partly from the Java world, I have been reaching for the builder pattern part of the toolbox repeatedly, and have found that the pattern

fn set_x<'a>(&'a mut self, v: T) -> &'a mut Self

is extremely familiar and useful. So much so that I’ve been wondering if there is a reasonable way to add some liftime elision or sugar of some kind to make it a more obvious and less noisy one.

2 Likes

It already works fine with Lifetime elision, doesn’t it?

fn set_x(&mut self, v: T) -> &mut Self

Doh. You are right. I was recalling an earlier issue. It is now a good and simple pattern to use.

That pattern doesn’t work if self is not mutable, and you want to move it to a new value.

let a = Foo::new(); let b = a.set_x(…);

It would be a bit inconvenient for that situation, but I think explicitly cloning the object would be a reasonable solution for when you really do want that.

let a = Foo::new();
let mut b = a.clone();
b.set_x(...);
// or, to avoid mutability more
let b = {
    let mut temp = a.clone();
    temp.set_x(...);
    temp
};

It certainly isn’t as nice as just having the methods return a new object, but I would guess the majority of people using setters will just be wanting to use them on the same object, and in that case using references is cheaper than implicitly cloning.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.