I believe when type-checking dyn Trait<'a>
, the methods that it contains are not taken into account.
This is because it is always backwards-compatible to add new methods to a trait as long as the new methods have a default definition. So everything that compiles with your definition of Trait
must also compile when a new method is added to Trait
.
Here's an example added method which would make your code unsound:
trait Trait<'a> {
fn zog(&self) -> &'a str;
fn bog(&mut self, to_store: &'a str) { }
}
struct StoresBog(Option<&'static str>);
impl Trait<'static> for StoresBog {
fn zog(&self) -> &'static str {
self.0.unwrap_or("hi")
}
fn bog(&mut self, s: &'static str) {
self.0 = Some(s);
}
}
With a trait modification like this, promoting dyn Trait<'static>
to dyn Trait<'a>
shouldn't be allowed: dyn Trait<'static>
only needs to accept &'static str
strings, but dyn Trait<'a>
must be able to accept any string, even one which is less than static lifetime.
As an example, a struct which implements Trait<'static>
, StoresBog
, clearly does not implement Trait<'a>
for any non-'static
'a
. It's only possible to store &'static str
in a variable declared for &'static str
- you can't pass a str referencing a heap allocated String
into StoresBog::bog
; StoresBog
does not implement Trait<'a>
.
In type theory language, dyn Trait<'a>
is invariant with respect to 'a
. More on variance here.
As a side note, to see that dyn Trait
is the problem here and not Foo
, this example also fails to compile:
trait Trait<'a> {
fn zog(&self) -> &'a str;
}
impl<'a> Trait<'a> for () {
fn zog(&self) -> &'a str { "" }
}
fn foo() ->Box<dyn Trait<'static>> {
Box::new(())
}
fn bar<'a>() -> Box<dyn Trait<'a>> {
foo()
}
According to that above link about variance in the nomicon Box<T>
is covariant with respect to T
. So if dyn Trait<'static>
was a subtype of dyn Trait<'a>
, then Box<dyn Trait<'static>>
would also be a subtype of Box<dyn Trait<'a>>
.