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.