Foreword:
Original case I tries to implement is: "Replace all signature's types with associated types in impl block".
Synopsis:
Variance inference looks weird in case described above. When &'s T
type is replaced with Self::AssocT<'s>
it drives a signature mismatch.
trait Test
where
Self: for<'a> Lookup<'a>,
{
fn test<'s>(_: &'s String) -> &'s String;
}
trait Lookup<'s> {
type Arg;
}
impl<'a> Lookup<'a> for () {
type Arg = &'a String;
}
impl Test for () {
fn test<'s>(_: <Self as Lookup<'s>>::Arg) -> <Self as Lookup<'s>>::Arg { // Error: `'s` is not `'s`
todo!()
}
}
According to nomicon and dev-guide, by default 'a
should be late-bounded and contravariant in fn(&'a T)
signature, so Self::AssocT<'a>
probably changes variance of 'a
in some cases and its doesn't looks like a normal behavior.
trait CovariantLate: Lookup {
fn test<'a>(arg: i32) -> &'a i32;
}
trait CovariantEarly: Lookup {
fn test<'a: 'a>(arg: i32) -> &'a i32;
}
trait ContrvariantLate: Lookup {
fn test<'a>(arg: &'a i32) -> i32;
}
trait ContrvariantEarly: Lookup {
fn test<'a: 'a>(arg: &'a i32) -> i32;
}
// or it is bivariant?
trait InvariantLate: Lookup {
fn test<'a>(arg: &'a i32) -> &'a i32;
}
trait InvariantEarly: Lookup {
fn test<'a: 'a>(arg: &'a i32) -> &'a i32;
}
trait Lookup {
type T1<'a>;
type T2<'a>;
type T3;
}
impl<T: ?Sized> Lookup for T {
type T1<'a> = &'a i32;
type T2<'a> = i32;
type T3 = i32;
}
impl CovariantLate for () {
fn test<'a>(arg: Self::T2<'a>) -> Self::T1<'a> {
todo!()
}
}
impl CovariantLate for ((), ) {
fn test<'a>(arg: Self::T3) -> Self::T1<'a> {
todo!()
}
}
impl CovariantEarly for () {
fn test<'a>(arg: Self::T2<'a>) -> Self::T1<'a> {
todo!()
}
}
impl CovariantEarly for ((), ) {
fn test<'a>(arg: Self::T3) -> Self::T1<'a> {
todo!()
}
}
// errors
// impl ContrvariantLate for () {
// fn test<'a>(arg: Self::T1<'a>) -> Self::T2<'a> {
// todo!()
// }
// }
impl ContrvariantLate for ((), ) {
fn test<'a>(arg: Self::T1<'a>) -> Self::T3 {
todo!()
}
}
impl ContrvariantEarly for () {
fn test<'a>(arg: Self::T1<'a>) -> Self::T2<'a> {
todo!()
}
}
impl ContrvariantEarly for ((), ) {
fn test<'a: 'a>(arg: Self::T1<'a>) -> Self::T3 {
// ^^^^^^ required
todo!()
}
}
// errors
// impl InvariantLate for () {
// fn test<'a>(arg: Self::T1<'a>) -> Self::T1<'a> {
// todo!()
// }
// }
impl InvariantEarly for () {
fn test<'a>(arg: Self::T1<'a>) -> Self::T1<'a> {
todo!()
}
}
The workaround is specify 's: 's
bound in method definition, but this drives another mismatch in "normal" implementations.
trait Test
where
Self: for<'a> Lookup<'a>,
{
fn test<'s>(_: &'s String) -> &'s String
where
's: 's; // Now it is early-bounded.
}
trait Lookup<'s> {
type Arg;
}
impl<'a> Lookup<'a> for () {
type Arg = &'a String;
}
impl<'a> Lookup<'a> for ((),) {
type Arg = &'a String;
}
impl Test for () {
fn test<'s>(_: <Self as Lookup<'s>>::Arg) -> <Self as Lookup<'s>>::Arg {
todo!()
}
}
impl Test for ((),) {
fn test<'s>(_: &'s String) -> &'s String { // Error: `'s` is not `'s`
todo!()
}
}