1

I ran into a confusing situation where what the compiler outputs doesn't logically make sense. Here is the minimal example to reproduce the same issue I'm having with my project code.

use std::sync::Arc;

struct A<'a, T> {
    f: Box<dyn Fn(&u32) -> T + 'a>
}

struct B<'a> {
    inner: A<'a, Z<'a>>
}

impl<'a, T> A<'a, T> {
    fn new<F>(f: F) -> Self where F: Fn(&u32) -> T + 'a {
        A { f: Box::new(f) }
    }
}

struct X<'a> {
    _d: &'a std::marker::PhantomData<()>
}

struct Z<'a> {
    _d: &'a std::marker::PhantomData<()>
}

impl<'a> X<'a> {
    fn g(&self, y: u32) -> Z {
        Z { _d: &std::marker::PhantomData }
    }
}

impl<'a> B<'a> {
    fn new(x: Arc<X<'a>>) -> Self {
        B {
            inner: A::new(move |y: &u32| -> Z {
                x.g(*y)
            })
        }
    }
}

fn main() {
}

And the confusing compilation error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> t.rs:35:19
   |
35 |                 x.g(*y)
   |                   ^
   |
note: first, the lifetime cannot outlive the lifetime '_ as defined on the body at 34:27...
  --> t.rs:34:27
   |
34 |             inner: A::new(move |y: &u32| -> Z {
   |                           ^^^^^^^^^^^^^^^^^^^
note: ...so that closure can access `x`
  --> t.rs:35:17
   |
35 |                 x.g(*y)
   |                 ^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 31:6...
  --> t.rs:31:6
   |
31 | impl<'a> B<'a> {
   |      ^^
   = note: ...so that the expression is assignable:
           expected B<'a>
              found B<'_>

error: aborting due to previous error

What I don't quite get is what "the lifetime" refers to as mentioned in the log, and what exactly that anonymous lifetime '_ stands for.

Determinant
  • 3,886
  • 7
  • 31
  • 47

1 Answers1

2

There was a small oversight in your code. It should be:

impl<'a> X<'a> {
    fn g(&self, y: u32) -> Z<'a> {
        Z { _d: &std::marker::PhantomData }
    }
}

The whole thing then compiles. This is an example of Rust's lifetime elision rule coming into play.

According to the relevant elision rules, namely:

  • Each elided lifetime in input position becomes a distinct lifetime parameter.
  • If there are multiple input lifetime positions, but one of them is &self or &mut self, the lifetime of self is assigned to all elided output lifetimes.

Then to rustc your original code will actually be like following:

impl<'a> X<'a> {
    fn g<'b>(&'b self, y: u32) -> Z<'b> {
        Z { _d: &std::marker::PhantomData }
    }
}

The elided lifetime parameter 'b will come from the calling site, which is exactly what you saw in the error message. The rustc couldn't reconcile the two lifetimes, hence the error.

edwardw
  • 12,652
  • 3
  • 40
  • 51
  • Thanks for asking so promptly! So I do know about the elision rule... and actually I wrote 'b explicitly previously. But what I didn't quite get, intuitively, is why rustc couldn't reconcile the two lifetimes. Could you point that out? – Determinant Nov 17 '19 at 06:45
  • @Determinant I believe that's because the variance. Unless you explicitly define `'b: 'a`, the lifetimes in Rust are invariant, meaning there's no relationship between them. That's what `rustc` tells you in the error message, `'_` and `'a` are two different lifetimes. – edwardw Nov 17 '19 at 06:56
  • I also tried that, but without luck...Could you show how to do it (with 'b: 'a bound) correctly? I think that'd be very educational. – Determinant Nov 17 '19 at 07:22
  • So my reasoning was: although the def for `g` doesn't specify the relation between `'a` and the implicit `'b`, `x.g` still gives the information that `&self` in g, i.e. `'b` should be bounded by the lifetime of `x`, which is bounded by `Arc>`, which is bounded by `'a`. So in this sense, `'a` should outlive `'b`? Most part of my confusion came from this. – Determinant Nov 17 '19 at 07:34
  • 1
    @Determinant a flaw in your reasoning, *`'b` should be bounded by the lifetime of `x`, which is bounded by `Arc>`*. Not true, remember a closure is a suspended computing, which doesn't happen yet. `'b` is only materialized whenever `B::inner::f` is called. `rustc` can't possibly know what that is. So unless it can statically determines all lifetimes involved, it will complain. `fn g(&self, y: u32) -> Z<'a>` does just that, which was the only place in your original code that depended on elided lifetime. – edwardw Nov 17 '19 at 08:27
  • 1
    Minor correction: relationships between lifetimes are a different thing from [variance](https://doc.rust-lang.org/nightly/nomicon/subtyping.html), which is a relationship between a type constructor and its parameter. I don't know if there's a good name for the relationships between lifetimes but you could say that `'b` and `'a` are *independent* unless explicitly constrained. (Actually, because `'b` is already constrained not to strictly outlive `'a`, they are only partially independent; adding `'b: 'a` would constrain them to be the same.) – trent Nov 17 '19 at 13:52
  • @trentcl thanks, saying they are independent would be better. – edwardw Nov 17 '19 at 14:19
  • @trentcl I see. So `rustc` does know that `'a: 'b` has to hold -- which is what I thought. But no matter how I annotate the other part `'b: 'a`, the compiler still doesn't seem to like what I assert...Could you elaborate on this? – Determinant Nov 17 '19 at 17:42
  • 1
    `'b: 'a` is a mistake because it says that `'b` must outlive `'a`. `'a` must already outlive `'b`, so adding the annotation just says that `'a` and `'b` must be the exact *same* lifetime -- in other words, it's equivalent to `fn g(&'a self, y: u32) -> Z<'a>`. Writing `&'a self` is nearly always a mistake because it forces `self` to be borrowed for `'a`, and when `'a` is the lifetime of something inside `self`, the lifetimes can become overconstrained. [This recent question is about a bug in a library caused by just such a misplaced `'a`.](https://stackoverflow.com/q/58878192/3650362) – trent Nov 17 '19 at 22:59
  • 1
    Applied to your problem, you can't make `new` compile by *adding* a constraint to `g` (forcing `'b` to outlive / be the same as `'a`), because the problem is that the constraints are contradictory. You need to *remove* a constraint (allow `'b` to be independent of `'a`). – trent Nov 17 '19 at 23:01