Confused lifetime error

pub trait Driver {
    type Arguments<'a>;
}

struct StringWriter<'a, D> 
where
    D: Driver
{
    args: D::Arguments<'a>
}

impl<'a, D> Writer<'a, D> for StringWriter<'a, D> 
where
    D: Driver
{
    fn push_args(&mut self, args: D::Arguments<'a>) {
        self.args = args;
    }
}

pub trait Writer<'writer, D: Driver> {
    fn push_args(&mut self, args: D::Arguments<'writer>);
}

pub trait Expression {
    type Driver: Driver;

    fn encode<'writer, W>(self, writer: &mut W)
    where
        W: Writer<'writer, Self::Driver>,
        Self: 'writer;
}

struct Foo<'a, D>
where
    D: Driver,
{
    args: D::Arguments<'a>,
}

impl<'a, D> Expression for Foo<'a, D>
where
    D: Driver,
{
    type Driver = D;

    fn encode<'writer, W>(self, writer: &mut W)
    where
        W: Writer<'writer, D>,
        Self: 'writer
    {
        writer.push_args(self.args);
    }
}

The compiler shows

error: lifetime may not live long enough
  --> src/lib.rs:52:9
   |
41 | impl<'a, D> Expression for Foo<'a, D>
   |      -- lifetime `'a` defined here
...
47 |     fn encode<'writer, W>(self, writer: &mut W)
   |               ------- lifetime `'writer` defined here
...
52 |         writer.push_args(self.args);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'writer` must outlive `'a`
   |
   = help: consider adding the following bound: `'writer: 'a`

Someone can tell me why 'writer should outlive 'a? How to infer?

Because of

fn push_args(&mut self, args: D::Arguments<'writer>);

Arguments have 'writer lifetime (maybe some misuse of 'writer' word for arguments, but ok), but in

impl<'a, D> Expression for Foo<'a, D>

we have 'a lifetime for D::Arguments. That's why compiler requires that 'writer must outlive 'a. You can't add this constraint for encode on impl block because of " impl has stricter requirements than trait". But you can change your trait

pub trait Expression<'writer> {
    type Driver: Driver;

    fn encode<W>(self, writer: &mut W)
    where
        W: Writer<'writer, Self::Driver>,
        Self: 'writer;
}

Thanks for your reply! But I think that 'a should live longer than 'writer, because args: D::Arguments<'writer> means that args should live as long as the writer, which implies that Foo<'a, D> should live as long as 'writer, right?

args: D::Arguments<'writer> means that Foo defers to D's implementation of Driver to define what the type of args is. There's no inherent lifetime restrictions between the GAT parameter and the type. You may find GATs where Argument<'short> is &'static str or Argument<'static> is &'not_static i32.

Due to how lifetime well-formedness works, your Foo<'short, D> will only be valid for 'short even if D::Arguments<'short> = &'static str, and a Foo<'static, D> won't be valid for 'static if D is some non-'static type, for example &'a str (which might have defined Arguments<'static> = &'a str).

I'm not sure this contradicts what you're trying to say, but, FYI I guess.


So the problem is you bounded on something that takes a D::Arguments<'writer> for a caller chosen 'writer and then tried to pass it a D::Arguments<'a>, where 'a is something specific to the implementor. If you don't want to thread the lifetime through the trait like @filtsin suggested, you need some way to go from D::Arguments<'a> to D::Arguments<'writer>. This can't happen automatically; you have to line up the lifetimes exactly, just like you would if the lifetime was a trait parameter. [1]

You do know that Self: 'writer, which here means Foo<'a, D>: 'writer, which means that 'a: 'writer and D: 'writer. (Perhaps this is what you meant by "args should live as long as the 'writer?") So that means you need a way to go from D::Arguments<'long> to D::Arguments<'short>.

That's a covariant lifetime -- a lifetime that can "shrink" -- like you're probably used to from references.


If you need something like covariance for your GAT, you have to "manually" build that into your trait and manually utilize it, too. There's nothing that says you have to define a GAT to be a covariant type -- you could easily define one to be contravariant or invariant type. And that is why you have to get the lifetimes to line up exactly -- the compiler can't assume it's valid to change a GAT lifetime parameter in any way, similar to trait lifetimes.

Here's one way to build in covariance:

pub trait Driver {
    type Arguments<'a>;
    fn covar<'long: 'short, 'short>(_: Self::Arguments<'long>)
        -> Self::Arguments<'short>;
}
-        writer.push_args(self.args);
+        writer.push_args(D::covar(self.args));

(I'd share a playground but it's busted at the moment.)


  1. More on that in a second. ↩︎

The playground is still mostly spinning, but I managed to get a gist for when it does work.

I am still confused.

  1. the field args: D::Arguments<'a> implies that args lives as long as Foo, maybe longer, right?
  2. Self: 'writer implies that the encode is only available when Foo lives long as the Writer, right?
  3. Based on 1 and 2, the value of the field args lives as long as the Writer at least, right?

If those 3 conditions are proper, why I should do a transform manually?

Sorry, I made a mistake. D::Arguments<'a> is not just &' value, there are many cases that don't support covariant with 'a, so I must handle it case by case.

I was going to write up a little example of types where it's not valid to shorten a lifetime (variance) when more convenient, but it sounds like you understand.

Thank you a lot :slight_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.