Ref 'cannot infer an appropriate lifetime due to conflicting requirements' problem

#1
  1. I have the following code. Sorry for the abstractness, it’s a “minimal example”

pub trait GetThingT<'a, Thing: ?Sized> {
    fn get_thing(&'a self) -> &'a Thing;
    // fn maybe_get_thing(&'a self) ->  Option<&'a Thing>;
}

pub struct HasThing<Thing> {
    pub thing: Thing,
    // pub maybe_thing: Option<Thing>
}

impl<'a, Thing> GetThingT<'a, Thing> for HasThing<Thing> {
    fn get_thing(&'a self) -> &'a Thing {
        & self.thing
    }


    /*
    fn maybe_get_thing(&'a self) -> Option<&'a Thing> {
        let thing = self.maybe_thing.as_ref()?;
        Some(thing)
    }
    */
}

pub struct Wrapper<'a, Obj: ?Sized> {
    pub inner: &'a Obj
}


impl<'a, Obj> Wrapper<'a, HasThing<Obj>> {

    pub fn compiles(&self) -> Wrapper<'a, HasThing<Obj>> {
        Wrapper {
            inner: self.inner
        }
    }

    pub fn also_compiles(&self) -> &'a GetThingT<'a, Obj> {
        let t: &'a GetThingT<'a, Obj> = self.inner;
        t
    }

    pub fn does_not_compile(&self) -> Wrapper<'a, GetThingT<'a, Obj>> {
        let t: &'a GetThingT<'a, Obj> = self.inner;
        Wrapper {
            inner: t,
        }
    }

}
  1. fn compiles and fn also_compiles works as expected. The failure of fn does not compile confuses me, as all we do is wrap it in a Wrapper object.

  2. What am I doing wrong, and how do we fix this?

#2

In particular, via fn compiles, we show that it is okay to “Wrap”.

Via fn also_compiles, we show it is okay to cast from HasThing to GetThingT

Yet, when we combine the two, we get a weird lifetime error.

#3

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f4f336a0a87892fefefd9a9e4efffcdc playground link

#4

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: https://doc.rust-lang.org/nomicon/lifetime-elision.html

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.

2 Likes
Help refactoring this code
#5
  1. Thanks for the detailed reply.

  2. The keyline is pub struct Wrapper<'a, Obj: ?Sized + 'a> { Everything works now.

  3. I agree on lifetime ellision. For this particular example, I was running into issues and decided to manually label everything just to see if what I had in mind == what rustc has in mind.

=====

  1. “refs in Structs” I’m not sure if I can avoid this issue.

I am writing a Tensor library in Rust. These tensors supports “views” (which means same underlying storage, but different different offset / increments, to get a ‘slice’ of the tensor).

My choices seem to be: 1. use Rc everywhere (but I don’t want to pay the ref counting cost, even if it’s tiny) or 2. see if I can get it all working with refs (which has been an enlightening experience.)

For this particular problem of Tensors + “views” (sharing underlying storage), given the choice to not use Rc’s, is there another design pattern to get around the issue of “refs in structs” ?

#6

That’s cool, good luck with your project! I think you will need to use ref in structs or unsized types, but the latter will require unsafe code to get working.

Unsized type:

#[repr(transparent)]
struct Row([f32]);

You will need to use unsafe to make one from a slice.