use std::{marker::PhantomData};
trait DoSomething<T> {
fn do_sth(&self, value: T);
}
impl<'a, T> DoSomething<T> for &'a usize {
fn do_sth(&self, value: T) {
}
}
fn foo<'a>(b: &dyn DoSomething<&'a usize>) {
let s: usize = 10;
b.do_sth(&s) // #1 error[E0597]: `s` does not live long enough
}
struct DoSomething2<T>{
v:PhantomData<T>
}
impl<T> DoSomething2<T>{
fn do_sth(&self, v:T){}
}
fn foo2<'a>(b:&DoSomething2<&'a usize>){
let s = 10;
b.do_sth(&s); // #2
}
fn main() {
}
#1 is an error but #2 is ok. What's the reason here? It sounds like the covariance of the lifetime parameter behind the trait object is restricted.
IMO, in the second case, the lifetime 'a can be shrunk to the innermost block of foo2 in order to make the lifetime of the reference of s can satisfy the 'a. However, the first case is a bit different.
I think this is similar to an issue I ran into a couple weeks ago. @quinedot had a good example of how interior mutability can be used behind a trait object to cause problems, though this example is a little different than mine was [1]
Essentially, you could create a type with interior mutability that implemented the trait and captured the reference passed to that method. If the borrow checker allowed your code to work, that captured reference could be pointing to a completely different variable's storage when it accessed it later.
in fact a reference to a trait object worked just fine in my case, it was only an owning trait object where the problems started ↩︎
Is that to say, the lifetime in a trait object is invariant? In my example, it embodies that the s defined in the function's body should outlive 'a, which would be over its maximum scope.
While variance together with trait objects is a bit weird, I don't think this is the case here.
In #1 you have a type that implements DoSomething<&'a usize> and you know it's valid for that 'a only. Someone might implement DoSomething<&'static usize> for their type and pass an instance of that type to your foo function, in that case calling do_sth with &s would not be valid.
Note that even if variance worked with trait objects this would still be wrong! That's because DoSomething<T> at best can be contravariant in T since it reads it.
#2 instead works because the compiler statically knows that DoSomething2 is covariant.
ps: the impl<'a, T> DoSomething<T> for &'a usize is useless in this example, maybe that's confusing you?
The book says nothing because traits currently don't have variance. The fact that DoSomething<T> (the trait) can at best be contravariant in T is just a logical fact. If it could be covariant it would result in unsoundness with just safe code (like passing a non-'static lifetime where a 'static lifetime was expected).
Higher-rankedfunction pointers and trait objects have another subtype relation. They are subtypes of types that are given by substitutions of the higher-ranked lifetimes. Some examples: ...
The second case is failed if we make it invariant over T:
If b can DoSomething for any lifetime, no matter how short (or long) -- that's what the HRTB means -- than it can DoSomething for the local borrow in foo.
I don't know that I really agree with that wording, it makes it sound like a HRTB makes a function strictly more expressive. It's more flexible for the function body but more restrictive for callers of the function.
A trait bound (HR or not) is part of a contract:
Callers must meet the bound or they can't call the function
Inside the body of the function, you can assume the bound was met
In this case it makes it possible to utilize the trait with a local borrow (which is a typical reason to need a HRTB: you need something to work with a lifetime shorter than any caller can name)
So while it's true in this case that means the "lifetime parameter accept[s] any lifetime", it also made it so you can't call the function with something that doesn't work for every lifetime.
I think it is beyond my knowledge of rust. This part is obscure to me. Especially for the case bar(&&local_unit);. Since the borrow of the block variable i within the function body can satisfy the lifetime declared by the HRTB, why does the lifetime of the &local_unit whose lifetime definitely lives longer than the borrow of the local variable not satisfy the bound?