Consider the following code:
trait MyTrait<'a, 'b: 'a> {
fn f(&'b mut self) -> &'a str;
}
struct MyStruct {
my_string: String,
}
impl<'a, 'b: 'a> MyTrait<'a, 'b> for MyStruct {
fn f(&'b mut self) -> &'a str {
&self.my_string
}
}
fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
bar(s);
bar(s); // ERROR
}
fn foo2<'a>(s: &mut MyStruct) {
bar(s);
bar(s);
}
fn bar<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
let x = s.f();
}
The trait MyTrait
defines a function that returns a str
reference. Its lifetime can be interpreted as "the object that returned the str
reference will live at least as long as that reference.".
bar
is a function that accepts a mutable reference to a struct that implements MyTrait
and calls f
. The functions foo
and foo2
are identical with the only difference being that foo2
accepts a specific type while foo
accepts any concrete type that implements MyTrait
.
Since foo2
compiles, I expected foo
to compile as well but it didn't. Here is the error message:
error[E0499]: cannot borrow `*s` as mutable more than once at a time
--> src\bin\main2.rs:17:6
|
15 | fn foo<'a, 'b: 'a>(s: &'b mut impl MyTrait<'a, 'b>) {
| -- lifetime `'b` defined here
16 | bar(s);
| ------
| | |
| | first mutable borrow occurs here
| argument requires that `*s` is borrowed for `'b`
17 | bar(s); // ERROR
| ^ second mutable borrow occurs here
From what I understand, here is what is going on: MyTrait
forces the object that implements it live for the lifetime 'b
. The first call to bar
borrows s
for the lifetime 'b
, meaning that the borrow won't drop at the end of bar
like a regular borrow, but will drop only at the end of 'b
. foo
and bar
have the same function signature and therefore the lifetimes specified will be identical. This means that when bar
mutably borrows s
, this borrow will only go out of scope at then end of 'b
- at the end of foo
. The second bar
call will follow the same logic, however since there already exists a mutable borrow, the compiler won't allow another one to be created.
Initially I thought a solution was to rewrite the signature of bar
as:
fn bar<'a, 'b: 'a, 'c: 'b>(s: &'b mut impl MyTrait<'a, 'c>)
Hoping the compiler would interpret it as: "'b
lives only for the duration of the function, 'a
, is even further constrained, and the object itself *s
lives for the lifetime 'c
which outlives the function".
However it resulted in the error:
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
Is it possible to constrain the borrow in bar
to only live for the duration of bar
?
CodePudding user response:
I'm not sure how you got to the code you posted, but I find the best rule of thumb is to start out with no explicit lifetime parameters, and only add them when the compiler says it needs them.
In your case, the compiler needs no lifetime parameters, it's perfectly happy to figure them all out itself:
trait MyTrait {
fn f(&mut self) -> &str;
}
struct MyStruct {
my_string: String,
}
impl MyTrait for MyStruct {
fn f(&mut self) -> &str {
&self.my_string
}
}
fn foo(s: &mut impl MyTrait) {
bar(s);
bar(s);
}
fn foo2(s: &mut MyStruct) {
bar(s);
bar(s);
}
fn bar(s: &mut impl MyTrait) {
let x = s.f();
}
In particular, for any function taking one reference and returning a reference, the compiler can work out that the returned reference must be from the argument, so no explicit bounds are needed. What it's actually doing (AFAIK) is basically inserting these lifetimes:
trait MyTrait {
fn f<'a>(&'a mut self) -> &'a str;
}
impl MyTrait for MyStruct {
fn f<'a>(&'a mut self) -> &'a str {
&self.my_string
}
}
Note how (1), the lifetime is on the function not on the trait. This is another good rule of thumb when adding lifetime parameters, always put them in the most specific scope possible. Also (2), there's no need for two parameters.
Edit:
As far as understanding why it didn't like your version specifically, I think it's basically because the lifetime is defined on the trait rather than on the function. The lifetime 'b
on MyTrait
in particular is a feature of the type, not of any particular code path. For all the compiler knows, for a generic MyTrait<'a, 'b>
those lifetimes could be tied to something else entirely elsewhere in the code, it has no way of knowing that 'b
cannot outlive your call to foo
because it has to be generic over all possible 'b
s.
But in foo2
, the compiler can see that all of the lifetimes of MyStruct
, it can collapse those lifetimes to the concrete case it has and knows that it only needs to borrow s
for the duration of bar
.