Why does the immutable version in the following example work, but the mutable version does not compile with error error: lifetime may not live long enough
?
trait Car {
fn honk(&self);
}
struct CarCollection {
cars: HashMap<usize, Box<dyn Car>>,
}
impl CarCollection {
// Does compile
fn get(&self, id: usize) -> Option<&dyn Car> {
self.cars.get(&id).map(|c| c.as_ref())
}
// Does not compile
fn get_mut(&mut self, id: usize) -> Option<&mut dyn Car> {
self.cars.get_mut(&id).map(|c| c.as_mut())
}
}
I know I can work around this issue with:
if let Some(car) = self.cars.get_mut(id) {
Some(car.as_mut())
} else {
None
}
But nonetheless, I am really confused by this and would like to know, what is going on here. I thought lifetimes do not differ between mutable and immutable references.
Playground
2
This has to do with trait object lifetime elision rules.
struct CarCollection {
cars: HashMap<usize, Box<dyn Car>>,
}
Here, the elision rules result in this implied lifetime:
struct CarCollection {
cars: HashMap<usize, Box<dyn Car + 'static>>,
}
Now let’s look at this method:
fn get_mut(&mut self, id: usize) -> Option<&mut dyn Car>
When we desugar the elided reference lifetimes, we get this:
fn get_mut<'a>(&'a mut self, id: usize) -> Option<&'a mut dyn Car>
Then, by the trait object lifetime elision rules, we get something like this:
fn get_mut<'a>(&'a mut self, id: usize) -> Option<&'a mut (dyn Car + 'a)>
This results in a lifetime mismatch when you attempt to return an Option<&mut (dyn Car + 'static)>
.
Note that this works in the shared reference case because the lifetimes of shared references can be implicitly shortened, but the rules for when this can happen with exclusive references generally disallow it. This is a complicated topic; for more information, see The Rustonomicon – Ownership – Subtyping and Variance – Variance.
The tl;dr here is that the lifetime of the trait object can be variant in the shared reference case, but not in the exclusive reference case. Therefore, the problem is easily fixed by making the lifetime of the returned trait object explicitly 'static
:
fn get_mut(&mut self, id: usize) -> Option<&mut (dyn Car + 'static)>
(Playground)
It may not be a bad idea to annotate the get
method the same way, as what you have now is artificially limiting what the caller can do with the returned reference by shortening the lifetime of the trait object to that of &self
.