What does this even mean?

If you have something like

pub trait Command<T, S, E> {
    fn run<'i>(
        &self,
        context: &CommandContext<'i, S, E>,
    ) -> Result<T, E> where E: 'i;
}

does this actually work? Can the error reference the input (labeled by the 'i lifetime)?

It means that E must not contain any references with a lifetime shorter than 'i.

But 'i is part of the fn, and E is part of the trait.

When you call run, you have to pick a value for 'i. You must pick a lifetime that is short enough to satisfy the requirement that E has no references shorter than 'i.

It's not really that E is referencing 'i or that 'i is referencing E here. It's just a constraint that something must be true, and it doesn't have a "direction".

2 Likes

To give a similar example, you can also do this:

fn to_i32<T>(val: T) -> i32
where
    i32: From<T>,
{
    i32::from(val)
}

As long as the left-hand-side is a type, it's happy. Can be generic or not.

1 Like

Ideally parse and run functions would be able to return errors like CommandError<'i> which is an error that refers to an input: &'i str, but we don't want the parser to be tied to a specific input: &'i str, only the parse results, and the error type needs to be known so that a command can specify requirements on it like so:

impl<E: CommandError> Command<(), (), E> for MyCommand {
    ...
}

(because there's no way to restrict what an fn accepts except by shoving it in the impl/trait)

If you try to actually implement what you described in words, you'll run into problems. In this implementation, we're implementing for any possible lifetime 'err. And we've bound the method to cases where E: 'r. Now, consider the case of 'err = 'static. The bound E = SpanErr<'static>: 'r holds for any possible 'r, so we have to implement the method... for any possible 'r. When 'r is strictly less than 'static, the body of the implementation tries to turn a &'r str into a &'static str by extending lifetime 'r. Hence, the error.

From a practical standpoint, without changing the trait as written, you can only return non-reference carrying errors.

The problem is that you don't really want E: 'r. You really want something like 'r: E. Unfortunately, this is not an accepted syntax.


You can add a lifetime parameter 'lt to the trait, and restrict 'r: 'lt. However, lifetimes on traits often lead to complications or unworkable bounds elsewhere, and now your trait is infected with that lifetime everywhere you write it out.

You can use a GAT on nightly. One downside here is that your implementation for SpanErr<'static> will still return a SpanErr<'r>, which might be confusing elsewhere. Or more generally speaking, your implementation ignores the lifetime of E and you always get back a lifetime of 'r. You might not notice if your lifetime-bound Es always match the input of CommandContext. On the upside, the signature of your trait is unchanged.

You can emulate the GAT on stable, using a helper super-trait. The same caveats apply.

Oh. So we'd actually need HKTs.

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.