I have this type:
struct Wrap<T>(Vec<T>);
I want to implement std::ops::Index
and return references to trait objects. This was my first attempt (Playground):
use std::ops::Index;
use std::fmt::Display;
impl<T> Index<usize> for Wrap<T>
where
T: Display
{
type Output = Display;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
This doesn't work and leads to this error:
error[E0310]: the parameter type `T` may not live long enough
--> src/main.rs:13:9
|
7 | impl<T> Index<usize> for Wrap<T>
| - help: consider adding an explicit lifetime bound `T: 'static`...
...
13 | &self.0[index]
| ^^^^^^^^^^^^^^
|
note: ...so that the type `T` will meet its required lifetime bounds
--> src/main.rs:13:9
|
13 | &self.0[index]
| ^^^^^^^^^^^^^^
I think I know why this happens: type Output = Display
is equivalent to type Output = Display + 'static
as every trait object carries a lifetime bound which defaults to 'static
.
So now I can just add the 'static
bound to my parameter T
, but this is over-constrained I think. I can easily implement such a method when not using an associated type:
impl<T> Wrap<T>
where
T: Display,
{
fn my_index(&self, index: usize) -> &Display {
&self.0[index]
}
}
No 'static
bound needed, because now the signature desugars to:
fn my_index<'a>(&'a self, index: usize) -> &'a Display + 'a
Which makes sense: the trait object has to live for at least 'a
. (Playground with all the code).
But can I make this work using associated types (like with the Index
trait)? I have the feeling that this might work with generic associated types, but (a) I'm not sure and (b) they are not implemented yet.
One attempt is to attach a lifetime to the impl:
// Note: won't work.
impl<'a, T> Index<usize> for Wrap<T>
where
T: Display + 'a,
{
type Output = Display + 'a;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
However, the compiler will not accept it because 'a
is not used.
error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
--> src/main.rs:7:6
|
7 | impl<'a, T> Index<usize> for Wrap<T>
| ^^ unconstrained lifetime parameter
There are several solutions suggested by the error code E0207, but since we cannot change the Index
trait, the only acceptable solution is to make Wrap
capture that unconstrained lifetime parameter:
use std::ops::Index;
use std::fmt::Display;
use std::marker::PhantomData;
struct Wrap<'a, T>(Vec<T>, PhantomData<&'a ()>);
// ^~ ^~~~~~~~~~~~~~~~~~~
impl<'a, T> Index<usize> for Wrap<'a, T>
where
T: Display + 'a,
{
type Output = Display + 'a;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
fn main() {
let w = Wrap(vec!['a', 'b'], PhantomData);
println!("{}", &w[0]); // prints "a"
let s = "hi".to_string();
let w = Wrap(vec![&s], PhantomData);
println!("{}", &w[0]); // prints "hi"
}
For sure, this will change your API and that extra lifetime will infect everywhere... If this is not acceptable, you could either
Index
trait, introduce your own lifetime-sensitive trait instead (thus users will need to use w.my_index(i)
instead of &w[i]
); orOutput = Display + 'static
, and exclude all transient types. Users will need to clone or use Rc
.