Puzzle piece number one: When you have a generic parameter, the caller of the function determines what "value" (concrete lifetime, concrete type...) that parameter takes. They have to respect any bounds that you set ('a: 'b, 'T: Clone, lifetimes being equal), but if they can meet the bounds, they can call your function.
For lifetimes, this means that when you have something like:
fn foo<'a>(_: &'a str) { /* ... */ }
The only thing you can assume about 'a are the bounds (none in this example), and that 'a lasts at least as long as the body of foo. In particular, 'a here can last for any arbitrary amount of time longer than the body of foo. It could be 'static for all you know.
You can't redefine 'a in the middle of your function. (Once a lifetime is determined, you can't redefine it at all, in fact. But things like reborrows and variance sometimes make it seem like you can.)
Puzzle piece number two: &mut and reborrows. Unlike &, &mut references do not implement Copy. If they did, they wouldn't be exclusive. However, you might wonder why you can do things like this then:
fn increment(i: &mut i32) {
*i += 1;
}
fn not_an_owner(i: &mut i32) {
increment(i);
// Wait, didn't `increment` consume `i`? It's not `Copy`...
increment(i);
increment(i);
increment(i);
// ...but I can keep passing it to `increment`?
}
Well, you can reborrow a mutable reference, and the reborrow can have a shorter lifetime than the original, which goes something like this:
fn not_an_owner(i: &mut i32) {
// Another exclusive borrow of the underlying `i32`.
// Rust knows that this borrow is dependent on `i`,
// so you won't be able to use `i` while `tmp` is
// around / you can't use `tmp` after you use `i` again.
let tmp = &mut *i;
// Pretend you "lost" `tmp` when you did this because it is not `Copy`
increment(tmp);
// Now for the next one... our original `tmp` has "expired".
// So we can use `i` again.
let tmp = &mut *i;
increment(tmp);
// Or just inline these reborrows...
increment(&mut *i);
// etc
}
Rust does this reborrowing for you normally. It would be much less ergonomic if it did not. That's why you can pass (reborrows of) a mutable reference around.
Puzzle piece number three: &mut and invariance. When you're dealing with lifetimes of borrows, you're probably used to dealing with them being covariant -- that means, the compiler can make the lifetime shorter if that will make things compile. So let's say I have something like this:
fn same_lifetime<'a>(_: &'a str, _: &'a str) {}
fn main() {
let static_ref: &'static str = "";
let local = "".to_string();
let local_ref: &str = &local; // Definitely not 'static
// This is ok -- `&'static str` will just automatically get shrunk
// to match `local_ref`'s lifetime. (Actually, both will probably
// be shrunk to be only as long as this function call!)
same_lifetime(local_ref, static_ref);
}
However, as it turns out, you break memory safety if you allow lifetimes to change behind &mut references. So if you have a &'a mut T, the lifetime of 'a can still be shortened -- but any lifetimes that are part of T cannot be changed. We say &mut T is invariant over T.
For example:
fn same_lifetime<'a>(_: &mut &'a str, _: &mut &'a str) {}
fn diff_lifetimes<'a, 'b>(a: &mut &'a str, b: &mut &'b str) {
// Error: We can't shrink `'a` and `'b` down to be some
// smaller lifetime where they are equal, because they
// are invariant inside the `&mut`s. Replace `&mut` with
// `&` to see that it works in that case.
same_lifetime(a, b);
}
Variance can be hard to wrap your head around in the general case. "Things behind &mut are more restricted than things behind &" is a good place to start though.
Alright, now let's try to tackle your error.
error[E0499]: cannot borrow `arg.unit.doto` as mutable more than once at a time
--> src/main.rs:16:35
|
14 | fn func2<'a>(arg : &'a mut Big<'a>) -> i32 {
| -- lifetime `'a` defined here
15 | let res = func1(arg);
| ----------
| | |
| | first mutable borrow occurs here
| argument requires that `*arg` is borrowed for `'a`
16 | println!("small doto {}", &mut arg.unit.doto);
| ^^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
func1 takes a &mut Big<'_>, same as func2. It's saying "first mutable borrow occurs here" because you're not giving up arg -- you try to use it later on. So there's an implicit reborrow going on when you call func1.
Big<'a> is behind a mutable reference, so it is invariant -- which means the 'a can't be shortened or otherwise changed when you call func1. The 'a in func2 and in func1 must be the same.
Your declaration of func1 said that the borrow of Big<'a> must last for 'a. Since 'a can't change, the exclusive borrow of Big<'a> you pass to func1 must also be 'a. That is:
- You're creating a reborrow with type
&'a mut Big<'a>, i.e.
- "argument requires that
*arg is borrowed for 'a"
How long is 'a? It's a type parameter input to this function, func2. It could be arbitrarily large. The one thing you know is that it definitely lasts throughout the body of func2, though.
So when you try to use arg again in the println!, it's still exclusively (mutably) borrowed by the reborrow you passed to func2. You just can't use it any more.
You could have fixed the OP by changing only func1 to not require the &mut to be as long as 'a. But you would have ran into the exact same sort of problem later when trying to use func2 from somewhere else.
&'a mut Thing<'a> is a red flag that your lifetimes are too restrictive / that you're requiring a lifetime equality that you shouldn't.