Another struggle with lifetimes: cannot infer an appropriate lifetime due to conflicting requirements

The very first problem is that I really don't understand what the compiler wants here and what's the problem, as in my opinion, the lifetimes here are perfectly valid and identical, so both traits should treat a reference of the same lifetime, but the compiler says the lifetimes are different and somehow any use of an object require static lifetime, even though a specific lifetime is specified ('a). Playground.

struct Intersection<'a, T: ?Sized> {
    object: &'a T,
    value: f32,
}

trait IntersectionsWith<'a, T: ?Sized> {
    fn intersections_with(&self, obj: &'a T) -> Vec<Intersection<'a, T>>;
}

struct Ray;

impl<'a, T> IntersectionsWith<'a, T> for Ray {
    fn intersections_with(&self, obj: &'a T) -> Vec<Intersection<'a, T>> {
        vec![]
    }
}

trait ColoredHit<'a, T: ?Sized> {
    fn colored_hit(&self, obj: &'a T) -> f32;
}

trait ObjectLike<T> {}

impl<'a, T> ColoredHit<'a, dyn ObjectLike<T>> for Ray
where
    Ray: IntersectionsWith<'a, dyn ObjectLike<T>>,
{
    fn colored_hit(&self, obj: &'a dyn ObjectLike<T>) -> f32 {
        let intersections = self.intersections_with(obj);
        0.0f32
    }
}
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:29:53
   |
29 |         let intersections = self.intersections_with(obj);
   |                                                     ^^^
   |
note: first, the lifetime cannot outlive the lifetime `'a` as defined here...
  --> src/main.rs:24:6
   |
24 | impl<'a, T> ColoredHit<'a, dyn ObjectLike<T>> for Ray
   |      ^^
note: ...so that the declared lifetime parameter bounds are satisfied
  --> src/main.rs:29:53
   |
29 |         let intersections = self.intersections_with(obj);
   |                                                     ^^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that the expression is assignable
  --> src/main.rs:29:53
   |
29 |         let intersections = self.intersections_with(obj);
   |                                                     ^^^
   = note: expected `&(dyn ObjectLike<T> + 'static)`
              found `&dyn ObjectLike<T>`

I have already tried specifying more lifetimes, being very explicit about what &self, and &obj lifetimes are and that all of them are distinct, tried specifying that the lifetimes are different and one is longer than the other like 'a: 'b but nothing helped, it just continued complaining, but this time it was that the object doesn't live long enough and it wasn't the 'static lifetime it complained about but 'a or the very first used.

You’re running into this:

Default trait object lifetimes

The assumed lifetime of references held by a trait object is called its default object lifetime bound. These were defined in RFC 599 and amended in RFC 1156.

These default object lifetime bounds are used instead of the lifetime parameter elision rules defined above when the lifetime bound is omitted entirely. If '_ is used as the lifetime bound then the bound follows the usual elision rules.

If the trait object is used as a type argument of a generic type then the containing type is first used to try to infer a bound.

  • If there is a unique bound from the containing type then that is the default
  • If there is more than one bound from the containing type then an explicit bound must be specified

If neither of those rules apply, then the bounds on the trait are used:

  • If the trait is defined with a single lifetime bound then that bound is used.
  • If 'static is used for any lifetime bound then 'static is used.
  • If the trait has no lifetime bounds, then the lifetime is inferred in expressions and is 'static outside of expressions.
// For the following trait...
trait Foo { }

// These two are the same because Box<T> has no lifetime bound on T
type T1 = Box<dyn Foo>;
type T2 = Box<dyn Foo + 'static>;

// ...and so are these:
impl dyn Foo {}
impl dyn Foo + 'static {}

// ...so are these, because &'a T requires T: 'a
type T3<'a> = &'a dyn Foo;
type T4<'a> = &'a (dyn Foo + 'a);

// std::cell::Ref<'a, T> also requires T: 'a, so these are the same
type T5<'a> = std::cell::Ref<'a, dyn Foo>;
type T6<'a> = std::cell::Ref<'a, dyn Foo + 'a>;
// This is an example of an error.
struct TwoBounds<'a, 'b, T: ?Sized + 'a + 'b> {
    f1: &'a i32,
    f2: &'b i32,
    f3: T,
}
type T7<'a, 'b> = TwoBounds<'a, 'b, dyn Foo>;
//                                  ^^^^^^^
// Error: the lifetime bound for this object type cannot be deduced from context

Note that the innermost object sets the bound, so &'a Box<dyn Foo> is still &'a Box<dyn Foo + 'static>.

// For the following trait...
trait Bar<'a>: 'a { }

// ...these two are the same:
type T1<'a> = Box<dyn Bar<'a>>;
type T2<'a> = Box<dyn Bar<'a> + 'a>;

// ...and so are these:
impl<'a> dyn Bar<'a> {}
impl<'a> dyn Bar<'a> + 'a {}

Your type &'a dyn ObjectLike<T> is short for &'a (dyn ObjectLike<T> + 'a) while the dyn ObjectLike<T> on its own e.g. in ColoredHit<'a, dyn ObjectLike<T>> or IntersectionsWith<'a, dyn ObjectLike<T>> stands for dyn ObjectLike<T> + 'static.

And then, the error message is misleading because it accepts the type signature of colored_hit despite it deviating from the trait declaration, because of variance. (A function accepting &'a (dyn ObjectLike<T> + 'a) can be coerced into a function accepting &'a (dyn ObjectLike<T> + 'static) and thus the trait implementation is accepted, and problems only appear in the implementation with the too unrestrictive function signature.)

In order to fix the problem, you can just make it matching (and more generic) by being explicit

impl<'a, 'b, T> ColoredHit<'a, dyn ObjectLike<T> + 'b> for Ray
where
    Ray: IntersectionsWith<'a, dyn ObjectLike<T> + 'b>,
{
    fn colored_hit(&self, obj: &'a (dyn ObjectLike<T> + 'b)) -> f32 {
        let intersections = self.intersections_with(obj);
        0.0f32
    }
}
3 Likes

Or you can try this:

trait IntersectionsWith {
    fn intersections_with<'a, T: ?Sized>(&self, obj: &'a T) -> Vec<Intersection<'a, T>>;
}
trait ColoredHit<T: ?Sized> {
    fn colored_hit(&self, obj: &T) -> f32;
}
impl<T> ColoredHit<dyn ObjectLike<T>> for Ray {
    fn colored_hit(&self, obj: &dyn ObjectLike<T>) -> f32 {
        let intersections = self.intersections_with(obj);
        0.0f32
    }
}
1 Like