'add_assign' in lambda function requires to mark the lambda functions variable as mutable, but 'add' with manual assignment no?

Why in case of add function mut keyword is not mandatory, but in case of add_assign it's mandatory?

use std::ops::{Add, AddAssign};

let mut ssa = String::new();
let rr = move || { // no mut for rr is needed
    ssa = ssa.add("ds");
    println!("{}", ssa);
};
rr();

let mut ssa = String::new();
let mut rr = move || { // mut is mandatory
    ssa.add_assign("ds");
    println!("{}", ssa);
};
rr();

The former closure doesn't implement FnMut, hence it can only be called once. I.e. multiple rr() calls will fail; but the first call consuming the rr means that it isn't considered to be mutated.

The second closure does implement FnMut, so the rr() call will use that interface which requires mutable access to the closure, but in turn doesn't consume it, so you could call rr() multiple times.

The reason why Rust won't allow you to call the first rr multiple times is that the ssa.add("ds") call could panic, leaving ssa ununitialized.

1 Like

The add() method consumes self (the ssa string, in this case), meaning the first rr actually implements FnOnce. Meanwhile, add_assign() takes &mut self so we mutate the ssa field in-place, which makes the second rr implement FnMut.

Same explanation, more links.


When you call ssa.add, you consume the captured variable. The compiler sees this and gives you back a closure that contains the captured ssa and implements FnOnce. Calling the closure consumes it (call_once takes self); because it's consumed, it doesn't need to be mutable. You can't call the top rr() more than once.

@steffahn gave a reason why the compiler can't see the assignment back to ssa and make this a FnMut.

When you call ssa.add_asign, ssa gets mutated in place (via &mut self) instead. The compiler sees this and gives you back a closure that contains the captured ssa and implements FnMut. You have to declare it mutable to use it because call_mut takes &mut self. But, you can call rr() more than once.

2 Likes

If you (for some reason) needed to use add in a closure that can be called multiple times, you could use std::mem::take in order to leave some intermediate default dummy value (in this case the empty string) in ssa until the result is successfully computed and assigned:

use std::ops::Add;
use std::mem;

let mut ssa = String::new();
let mut rr = move || {
    // mut for rr is mandatory now
    ssa = mem::take(&mut ssa).add("ds");
    println!("{}", ssa);
};
rr();
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.