I have some advice for you, don't annotate lifetimes until the compiler complains about it.
also in maybe_get_a_thing
, it can be written as
fn maybe_get_thing(&self) -> Option<&Thing> {
self.maybe_thing.as_ref()
}
here is a working version, I will explain it here
I removed the commented out stuff for brevity here
pub trait GetThingT<Thing: ?Sized> {
fn get_thing(&self) -> &Thing;
}
pub struct HasThing<Thing> {
pub thing: Thing,
}
impl<Thing> GetThingT<Thing> for HasThing<Thing> {
fn get_thing(&self) -> &Thing {
&self.thing
}
}
pub struct Wrapper<'a, Obj: ?Sized + 'a> {
pub inner: &'a Obj
}
impl<'a, Obj: 'a> Wrapper<'a, HasThing<Obj>> {
pub fn compiles(&self) -> Wrapper<'a, HasThing<Obj>> {
Wrapper {
inner: self.inner
}
}
pub fn also_compiles(&self) -> &'a dyn GetThingT<Obj> {
let t: &dyn GetThingT<Obj> = self.inner;
t
}
pub fn does_not_compile(&self) -> Wrapper<'a, dyn GetThingT<Obj>> {
let t: &dyn GetThingT<Obj> = self.inner;
Wrapper {
inner: t,
}
}
}
First with the GetThingT
trait, the function get_thing
now works for all lifetimes of self
, and you don't need to annotate lifetimes, which is tedious and very easy to get wrong. The desugaring after lifetime elison is
fn get_thing<'a>(&'a self) -> &'a Thing;
lifetime elsion rules: Lifetime Elision - The Rustonomicon
HasAThing
has not changed.
The impl gets simplified due to changes in GetThingT
.
Wrapper
has a new bound. Obj
must now outlive lifetime 'a
. This is there because the default, that Obj
must outlive 'static
, is too restrictive.
The impl block changed to reflect the change to Wrapper
.
The first functions didn't change much, beyond removing annotated lifetimes and using the more explicit dyn
syntax.
Overall this program has changed quite a bit. So a few things to note:
- Lifetime elision is your friend
- It is usually a bad idea to put references in structs, avoid it if you can
- If you need to put references in structs, make sure that what they refer to outlives their lifetime, this will ensure that you get the more useful lifetime when you use it elsewhere
-
'static
is usually not what you want, so annotate these lifetimes
- If you need to annotate lifetimes, try and annotate as little as possible. The Rust compiler is very good at inferring lifetimes.
- Use lifetimes to tell where some data came from for example
fn two_refs(string: &str, slice: &[u8]) -> (&[u8], &str) {
(&slice[1..], string)
}
This function has too many lifetimes in play, so the Rust compiler doesn't know how you want to link them up.
To help the compiler we can specify where
each return value came from.
fn two_refs<'a, 'b>(string: &'a str, slice: &'b [u8]) -> (&'b [u8], &'a str) {
(&slice[1..], string)
}
Also whenever you see a lifetime constraints i.e. 'b: 'a
or T: 'a
read this as lifetime 'b
/type T
outlives lifetime 'a
, this should help you understand lifetimes better.