I am hoping to make a little more progress in my understanding of lifetimes and so I'm trying to figure this out. What am I telling the compiler when I write this?
trait MyTrait<'a> {
fn foo(&'a self) -> &'a str;
}
and how is it different from when I write this?
trait MyTrait {
fn foo<'a>(&'a self) -> &'a str;
}
(the difference syntactically is that I moved the declaration of the <'a> lifetime to the method instead of the trait)
I had thought that in both cases I was simply declaring a lifetime parameter for use in the method signature, but that seems to be very incorrect because the first block produces unexpected error messages.
What I'm trying to express is "the str returned from foo() should have the same lifetime as the object implementing MyTrait". And to that end the second block seems to be the right way to say it. I'm also aware that in this case I can just leave out all the lifetime annotations and just let the compiler infer them, but part of the exercise I'm going through is to try to use explicit lifetime parameters everywhere to test my knowledge of what they mean.
The context where this came up is illustrated in this code, where the first syntax above produces errors, but the second one doesn't:
trait MyTrait<'a> {
fn borrow_contents(&'a self) -> &'a str;
}
struct Foo {
contents: String,
}
impl<'a> MyTrait<'a> for Foo {
fn borrow_contents(&'a self) -> &'a str {
&self.contents
}
}
fn main() {
let boxed_trait_object: Box<dyn MyTrait> = Box::new(Foo {
contents: "first".into(),
});
boxed_trait_object.borrow_contents(); // -> ERROR: borrowed value does not live long enough...
}
Replace the lifetime parameter with a type parameter; is it easier for you to understand now?
In this first snippet, the trait contains the (lifetime or type) parameter. That means the implementor is choosing what the parameter will be. A specific type can have multiple implementations for different parameters (coming from a single generic
implblocks or from multiple distinctimplblocks), but each implementation defines its ownfoo(), that takes its specifiedT(or the lifetime parameter). For example, you can implementMyTrait<u32> for FooandMyTrait<u64> for Foo, with different logic forfoo().With the second snippet, the method defines the parameter. That means the caller of the method is choosing what the parameter will be. The method defines a uniform interface and implementation that works with all types (or lifetimes) (satisfying on the constraints specified), and the caller chooses what type to put instead of
T.Those ways of declaring sometimes lead to the same result, because just like
fn foo<T>()defines a uniform interface and implementation, we can define a generic implimpl<T> MyTrait<T> for Foo, and that will also force a uniform interface and implementation. However, the discrepancies reveal themselves when we are forced into matching a specific implementation of the trait. Usually we are allowed in each call to call a different implementation, but in the following cases we are forced to pick one implementation only:for<T>), we cannot say we take "a type that implementsMyTrait<T>for anyT(U: for<T> MyTrait<T>), so we are forced to pick one concrete implementation. That means we can callfoo()in the second snippet with any type, since inside one implementationfoo()can take many types, but we are forced to callfoo()in the first snippet with only one type, the type we put in the trait bounds.Selfwith the same generic parameters, but not others. So we can call the second snippet with any type (because that is the interface we defined), but the first snippet only with the sameTas in implementing type, i.e. theTdeclared in the trait.Your example also fails because of this. By specifying
dyn MyTrait, you actually saydyn MyTrait<'_>, which means again a specific lifetime and therefore a specific implementation.These concerns apply more to type parameters and less to lifetime parameters because we have HRTB. Your example can also be fixed with them:
Still, it is less convenient, and therefore you should prefer specifying the parameters on the method when you can.
It seems the second snippet is always more general, so why we have the first? It is useful in the following cases:
Here, we cannot implement
foo()for any type, because we only have a singleTwe can hand (you may be confused because this is a generic implementation, for anyT; but for each concreteFoo<T>, we only have oneTwe can hand).Or with lifetimes, perhaps the type contain a reference with some specific lifetime and so cannot implement the trait with any lifetime. For example:
Again, here we cannot give any lifetime, only what we have.
Still, the more common case (especially with lifetimes) is to put the parameter on the method, and this is indeed the default: if you omit the lifetime, the elided lifetime will become a hidden parameter on the method. This:
Is equivalent to this:
Not this: