1
Fork 0

Improve description again

-- update summary based on review
-- rewrite explanation to be more clear and correct
This commit is contained in:
Matthew Kelly 2022-08-24 20:44:09 -04:00
parent 231e3a0415
commit dd7c48e529
3 changed files with 47 additions and 41 deletions

View file

@ -1,5 +1,5 @@
This error occurs when there is insufficient information for the rust compiler
to prove that a type has a long enough lifetime.
This error occurs when there is an unsatisfied outlives bound on a generic
type parameter or associated type.
Erroneous code example:
@ -13,58 +13,63 @@ trait NestedBorrowMut<U, V> {
impl<T, U, V> NestedBorrowMut<U, V> for T
where
T: BorrowMut<U>,
U: BorrowMut<V>, // error: missing lifetime specifier
U: BorrowMut<V>,
{
fn nested_borrow_mut(&mut self) -> &mut V {
self.borrow_mut().borrow_mut()
let u_ref = self.borrow_mut();
let v_ref = u_ref.borrow_mut();
v_ref
}
}
```
Why doesn't this code compile? The problem has to do with Rust's rules for
lifetime elision in functions (Chapter 10.3 in the Rust book). One of the
inputs is a reference to `self`, so the compiler attempts to assign the
the same lifetime to the `&mut self` input and `&mut V` output to the
`nested_borrow_mut()` function. The problem is that there is no way for the
compiler to directly figure out how these two lifetimes are related in the
implementation of the function. We're implementing the `NextedBorrowMut`
trait for a type `T`, so the `&mut self` reference has the lifetime of `T`.
We know that `T` implements the `BorrowMut` trait returning a reference to `U`,
and that `U` implements the `BorrowMut` trait returning a reference to `V`.
The key is that we have not told the compiler that those two `U` lifetimes
are the same: for all it knows, we could be that the first `BorrowMut` trait
on `T` works with a lifetime `'a` and the second `BorrowMut` trait on `U`
works on a lifetime `'b`.
Why doesn't this code compile? It helps to look at the lifetime bounds that
the compiler is automatically adding ("Lifetime Ellision", Chapter 10.3 in the
Rust book) to the `nested_borrow_mut` and `borrow_mut` functions. In both cases
the input is a reference to `self`, so the compiler attempts to assign the
the same lifetime to the input and output.
The fix here is to add explicit lifetime annotations that tell the compiler
that the lifetime of the output is in fact the same as the lifetime of the
input (`self`). There are three references involved, to objects of type `T`
(`self`), `U` (the intermediate type), and `V` (the return type). In the
working code below, we see that all have been given the same lifetime `'a`:
- `&'a mut self` in the function argument list for `T`
- `U: BorrowMut<V> + 'a` in the trait bounds for `U`
- `&'a mut V` in the function return for `V`.
Looking specifically at `nested_borrow_mut`,
we see that there are three object references to keep track of,
along with their associated lifetimes:
- `self` (which is a `&mut T`)
- `u_ref` (which is a `&mut U`)
- `v_ref` (which is a `&mut V`)
The compiler can the check that the implementation of the
`nested_borrow_mut()` function satisfies these lifetimes. There are two
functions being called inside of `nested_borrow_mut()`, both of which are
the `borrow_mut()` function, which promises that the output lifetime is
the same as the input lifetime (see lifetime elision rules), which checks out.
The `borrow_mut()` method implicitly requires that that the input and output
have the same lifetime bounds. Thus:
```rust
let u_ref = self.borrow_mut();
let v_ref = u_ref.borrow_mut();
```
Imply that `u_ref` and `self` must share a lifetime bound, and also that
`v_ref` and `u_ref` share a lifetime bound. The problem is that the function
signature for `nested_borrow_mut` only gives the compiler information about the
lifetimes of `self` and `v_ref` -- nothing about `u_ref`.
The way to fix this error is then to explicitly tell the compiler that the
lifetime of `u_ref` is the same as `self` and `v_ref`, which then allows it
to satisfy the two lifetime bound requirements described above.
Here is the working version of the code:
```rust
use std::borrow::BorrowMut;
trait NestedBorrowMut<'a, U, V> {
fn nested_borrow_mut(& 'a mut self) -> &'a mut V;
fn nested_borrow_mut(&'a mut self) -> &'a mut V;
}
impl<'a, T, U, V> NestedBorrowMut<'a, U, V> for T
where
T: BorrowMut<U>,
U: BorrowMut<V> + 'a, // Adding lifetime specifier
U: BorrowMut<V> + 'a,
{
fn nested_borrow_mut(&'a mut self) -> &'a mut V {
self.borrow_mut().borrow_mut()
let u_ref = self.borrow_mut();
let v_ref = u_ref.borrow_mut();
v_ref
}
}
```