Rust lifetimes confuse me again. I'm trying to return a mutable reference to a boxed object I own. Here is my problem simplified:
pub trait Foo {
fn foo(&self);
}
pub struct Bar {
foo: Option<Box<Foo>>,
}
impl Bar {
pub fn foo(&mut self) -> &mut Box<Foo> {
let foo_ref = self.foo.as_mut();
foo_ref.unwrap()
}
pub fn set_foo(&mut self, value: Box<Foo>) {
self.foo = Some(value);
}
}
I get these errors, which I don't really understand:
Compiling testproject v0.0.1 (file:///home/virtlink/projects/orion/testproject)
src/lib.rs:15:17: 15:25 error: cannot infer an appropriate lifetime due to conflicting requirements
src/lib.rs:15 foo_ref.unwrap()
^~~~~~~~
src/lib.rs:15:9: 15:25 note: first, the lifetime cannot outlive the method call at 15:8...
src/lib.rs:15 foo_ref.unwrap()
^~~~~~~~~~~~~~~~
src/lib.rs:15:9: 15:16 note: ...so that method receiver is valid for the method call
src/lib.rs:15 foo_ref.unwrap()
^~~~~~~
src/lib.rs:13:44: 16:6 note: but, the lifetime must be valid for the anonymous lifetime #1 defined on the block at 13:43...
src/lib.rs:13 pub fn foo(&mut self) -> &mut Box<Foo> {
src/lib.rs:14 let foo_ref = self.foo.as_mut();
src/lib.rs:15 foo_ref.unwrap()
src/lib.rs:16 }
src/lib.rs:15:9: 15:25 note: ...so that expression is assignable (expected `&mut Box<Foo>`, found `&mut Box<Foo>`)
src/lib.rs:15 foo_ref.unwrap()
^~~~~~~~~~~~~~~~
error: aborting due to previous error
Could not compile `testproject`.
I'm not sure how to solve this.
This is a case of lifetime elision and the default object bounds backfiring in a painfully invisible way.
The errors are downright opaque as it is, which is not good. If you replace the .as_mut().unwrap() with the comparable match statement:
match self.foo {
Some(ref mut foo) => foo,
None => panic!(),
}
things become a little clearer:
a.rs:13:34: 13:37 error: mismatched types:
expected `&mut Box<Foo>`,
found `&mut Box<Foo>`
(lifetime mismatch) [E0308]
a.rs:13 Some(ref mut foo) => foo,
^~~
a.rs:11:44: 16:6 note: the anonymous lifetime #1 defined on the block at 11:43...
a.rs:11 pub fn foo(&mut self) -> &mut Box<Foo> {
a.rs:12 match self.foo {
a.rs:13 Some(ref mut foo) => foo,
a.rs:14 None => panic!(),
a.rs:15 }
a.rs:16 }
note: ...does not necessarily outlive the static lifetime
error: aborting due to previous error
Now we know that what is happening is a lifetime mismatch in somewhere in the type &mut Box<Foo>, that an anonymous lifetime does not necessarily outlive the static lifetime. There are two lifetimes in that type; with no type elision, that type is &'a mut Box<Foo + 'b>. Remember that with a trait object, you still need to indicate how long the trait object can last for, hence 'b. In the most common case, Box<Trait> is equivalent to Box<Trait + 'static>, which indicates that the trait object cannot contain any non-static references. (Without this guarantee, memory safety would be violated.) In your struct definition, the lifetime of the trait object is inferred as 'static in this manner.
However, the elided lifetime in Box<Trait> is not always interpreted as 'static. If it is contained in a reference, then the required lifetime is shrunk to that, i.e. &'a Box<Trait> is interpreted as &'a Box<Trait + 'a>.
Therefore, the full elision-free signature of your method is actually this:
pub fn foo<'a>(&'a mut self) -> &'a mut Box<Foo + 'a>;
Now why this doesn’t work I’m not clear on; I would have thought that a &'a mut Box<Foo + 'static> (which you have) could be coerced to a &'a mut Box<Foo + 'a>, but apparently this is not the case; it might be a bug in variance handling (rejecting code that should be legal) or it might not be, I’m not sure. I don’t see why the anonymous lifetime #1 should be needing to outlive the static lifetime, that error feels like it’s handling the lifetimes back to front.
Anyway, what you actually wanted was to be returning a &'a mut Box<Foo + 'static>. So just write the 'static out explicitly and all is hunky‐dory:
pub fn foo(&mut self) -> &mut Box<Foo + 'static> {
self.foo.as_mut().unwrap()
}
Another solution is to require in the definition that a type implementing Foo must always be 'static. Then Box<Foo + 'a> would be obvious nonsense for an 'a not necessarily as great as 'static, and it gets sensible and knows that it’s got to be 'static. (Any lifetime constraint on the trait overrides the default object bounds.)
You can read more about the default object bounds in RFC 599.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With