Generic Associated Types (GATs) are coming to stable Rust in version 1.65, but will not support GAT traits with object safety yet at the initial release. However, there has existed a work-around for using generic associated lifetimes (with object safety) on stable Rust for a while.
So I wanted to try and see if the new GAT feature and the generic associated lifetime emulation can be combined on Rust 1.65 (although I tested this on nightly 1.66), but I'm running into some vague error messages that I'm hoping someone knows more about. I also considered posting this as a bug report on Github an on IRLO, but here seemed like the best place (please let me know if it's better to post this somewhere else).
At a very high level, there are 3 traits (Foo, Bar, Baz) which form a chain of nested GATs. The "Baz" trait has a GAT named "Bar" which must implement the "Bar" trait and the "Bar" trait has a GAT named "Foo" which must implement the "Foo" trait. The work-around I came up with seems to work okay, except for the work-around needed in order to make the "Baz" trait object safe. There are some errors that I can't seem to iron out when casting the struct Baz
to its dynamic helper trait generic::Baz
(from the generic
module) into a trait object. The errors happen in 2 specific places, I named them "error site A" and "error site B" in this example code at methods fn main
and fn bar
, respectively:
use std::marker::PhantomData;
#[derive(Default)]
pub struct Foo<'a>(PhantomData<&'a ()>);
#[derive(Default)]
pub struct Bar<'a>(PhantomData<&'a ()>);
#[derive(Default)]
pub struct Baz;
fn main() {
let _foo: Box<dyn generic::Foo + Send> = Box::new(Foo::default());
let _bar: Box<dyn generic::Bar + Send + Sync> = Box::new(Bar::default());
// Let's call the next line "error site A"
let _baz: Box<dyn generic::Baz + Send + Sync> = Box::new(Baz::default());
}
mod gat {
pub trait Foo<'foo> {}
impl<'foo> Foo<'foo> for super::Foo<'foo> {}
pub trait Bar<'bar> {
type Foo<'foo>: Foo<'foo>
where
Self: 'foo;
}
impl<'bar> Bar<'bar> for super::Bar<'bar> {
type Foo<'foo> = super::Foo<'foo>
where
Self: 'foo;
}
pub trait Baz {
type Bar<'bar>: Bar<'bar>
where
Self: 'bar;
fn bar(&self) -> Self::Bar<'_>;
}
impl Baz for super::Baz {
type Bar<'bar> = super::Bar<'bar>
where
Self: 'bar;
fn bar(&self) -> Self::Bar<'_> {
super::Bar::default()
}
}
}
mod generic {
// A trait alias for `Foo` which is object safe
// and can be sent between threads.
pub trait Foo<'foo>: super::gat::Foo<'foo> + Send {}
impl<'foo, T> Foo<'foo> for T where T: ?Sized + super::gat::Foo<'foo> + Send {}
// A trait alias for `Bar` which is object safe
// and can be sent and shared between threads.
pub trait Bar<'bar>: Send + Sync {}
impl<'bar, T> Bar<'bar> for T
where
T: super::gat::Bar<'bar> + Send + Sync,
for<'foo> <T as super::gat::Bar<'bar>>::Foo<'foo>: Send,
for<'foo> <T as super::gat::Bar<'bar>>::Foo<'foo>: super::generic::Foo<'foo>,
{
}
// A trait alias for `Baz` which is object safe
// and can be sent and shared between threads.
pub trait Baz: Send + Sync {
fn bar<'bar>(&'bar self) -> Box<dyn Send + Sync + Bar<'bar> + 'bar>;
}
impl<T> Baz for T
where
T: super::gat::Baz + Send + Sync,
for<'bar> <T as super::gat::Baz>::Bar<'bar>: Send + Sync,
// From here out it becomes tricky to pick the right impl trait bounds!
// There are a couple of scenarios that all give slightly different errors.
// See below for which scenario gives which errors.
// * Scenario 1): all the following bounds are commented
// * Scenario 2): only this next line uncommented
// for<'bar> <T as super::gat::Baz>::Bar<'bar>: super::generic::Bar<'bar>,
// * Scenario 3): only this next line uncommented
// for<'foo, 'bar> <<T as super::gat::Baz>::Bar<'bar> as super::gat::Bar<'bar>>::Foo<'foo>: Send,
// * Scenario 4): only this next line uncommented
// for<'bar, 'foo> <<T as super::gat::Baz>::Bar<'bar> as super::gat::Bar<'bar>>::Foo<'foo>: Send,
// * Scenario 5): only this next line uncommented
// for<'bar> <<T as super::gat::Baz>::Bar<'bar> as super::gat::Bar<'bar>>::Foo<'bar>: Send,
{
fn bar<'bar>(&'bar self) -> Box<dyn Send + Sync + Bar<'bar> + 'bar> {
let bar = <Self as super::gat::Baz>::bar(self);
// ... and this line is "error site B":
let bar: Box<dyn Send + Sync + Bar<'bar> + 'bar> = Box::new(bar);
bar
}
}
}
In the code there are a few "scenarios", which you can enable by uncommenting the corresponding line. I collected all the error messages for each scenario here:
Scenario 1)
When all impl trait bounds remain commented, then there is no error at
"error site A" in fn main
and the following error shows at error site B
in fn bar
. This is as expected, because we did not restrict Foo: Send
.
error[E0277]: `<<T as gat::Baz>::Bar<'bar> as gat::Bar<'bar>>::Foo<'foo>` cannot be sent between threads safely
--> examples/repr3.rs:91:64
|
91 | let bar: Box<dyn Send + Sync + Bar<'bar> + 'bar> = Box::new(bar);
| ^^^^^^^^^^^^^ `<<T as gat::Baz>::Bar<'bar> as gat::Bar<'bar>>::Foo<'foo>` cannot be sent between threads safely
|
= help: the trait `for<'foo> Send` is not implemented for `<<T as gat::Baz>::Bar<'bar> as gat::Bar<'bar>>::Foo<'foo>`
note: required for `<T as gat::Baz>::Bar<'_>` to implement `generic::Bar<'bar>`
--> examples/repr3.rs:58:19
|
58 | impl<'bar, T> Bar<'bar> for T
| ^^^^^^^^^ ^
= note: required for the cast from `<T as gat::Baz>::Bar<'_>` to the object type `dyn generic::Bar<'bar> + Send + Sync`
Scenario 2)
This time there is only an error at site A in fn main
and no error at site B.
Looking at other issues that mention "higher-ranked lifetime error", this seems
to be some kind of compiler limitation? Is there a work-around for this?
error: higher-ranked lifetime error
--> examples/repr3.rs:14:53
|
14 | let _baz: Box<dyn generic::Baz + Send + Sync> = Box::new(Baz::default());
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: could not prove `for<'a> Box<Baz>: CoerceUnsized<Box<(dyn generic::Baz + Send + Sync + 'a)>>`
Scenario 3)
This one causes errors at both site A in fn main
and in site B in fn bar
.
The first one at site A:
error: implementation of `gat::Bar` is not general enough
--> examples/repr3.rs:14:53
|
14 | let _baz: Box<dyn generic::Baz + Send + Sync> = Box::new(Baz::default());
| ^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `gat::Bar` is not general enough
|
= note: `gat::Bar<'0>` would have to be implemented for the type `Bar<'0>`, for any lifetime `'0`...
= note: ...but `gat::Bar<'1>` is actually implemented for the type `Bar<'1>`, for some specific lifetime `'1`
The other error at site B complains about 'static
lifetime. However,
I'm not sure where this 'static
lifetime comes from, as the 'bar
lifetime is hard-coded everywhere (possibly due to for<'foo, 'bar>
?):
error: lifetime may not live long enough
--> examples/repr3.rs:92:64
|
88 | fn bar<'bar>(&'bar self) -> Box<dyn Send + Sync + Bar<'bar> + 'bar> {
| ---- lifetime `'bar` defined here
...
91 | let bar: Box<dyn Send + Sync + Bar<'bar> + 'bar> = Box::new(bar);
| ^^^^^^^^^^^^^ cast requires that `'bar` must outlive `'static` ^^^^^^^^^^^^^ cast requires that `'1` must outlive `'static`
Scenario 4)
This scenario shows exactly the same errors as in scenario 3
(only the quantified lifetimes in the trait bound are now swapped,
just in case it would have mattered, but it doesn't).
Scenario 5)
Here we get the exact same errors as in scenario 1, because the
trait bounds only say something about 'bar
', but not about
the lifetime 'foo
.
Help
To end with some questions which I have about the above:
- Now that GATs are being stabilized, is such a work-around intended to be working from its first release?
- If not, does there exists a work-around for these errors or is there another way to achieve the same result?
- What is the cause of the error in scenario 2? Intuitively, I would expect that this would be the most straight-forward way to enforce the
Foo: Send
bound ongeneric::Bar<'bar>:::Foo<'foo>
, but the "higher-ranked lifetime error" suggests otherwise and I don't know how to work-around this one.
p.s. I am currently on rustc 1.66.0-nightly (0ca356586 2022-10-06)