Where bound for type equality with GAT without cycles

Consider this code:

use higher_kinded_types::ForLifetime;

pub trait LendingFn<T> {
    type Output<'a>
    where
        Self: 'a;
    fn call<'a>(&'a self, args: T) -> Self::Output<'a>;
}

trait Foo {
    type Bar;
    fn bar(&self) -> Self::Bar;
}

struct Baz<V>
where
    V: ForLifetime,
    for<'a> V::Of<'a>: Foo,
{
    bar: <V::Of<'static> as Foo>::Bar,
}

impl<V> Baz<V>
where
    V: ForLifetime,
    for<'a> V::Of<'a>: Foo,
{
    fn baz<F>(f: F) -> Self
    where
        F: for<'a> LendingFn<(), Output<'a> = V::Of<'a>>,
        // <V<'a> as Foo>::Bar = <V<'static> as Foo>::Bar
    {
        let foo = f.call(());
        let bar = foo.bar();
        Self { bar }
    }
}

Basically, I need to extract Bar from the output of a lending function, and that Bar in itself isn't the reason for GAT and will always be the same for any implementation. But I can't use <V<'a> as Foo>::Bar as <V::Of<'static> as Foo>::Bar because it must not be true. I would like to enforce in the where bound that those types end up equal so that code can rely on it.

Some of my attempts to write such a bound resulted in error[E0391]: cycle detected when computing the bounds for type parameter V.


My assumption is that Bar for types like that are considered different by the type system:

#[derive(Clone, Copy)]
struct FooI1<'a>(&'a u8);
impl<'a> Foo for FooI1<'a> {
    type Bar = &'a u8;
    fn bar(&self) -> Self::Bar {
        self.0
    }
}

#[derive(Clone, Copy)]
struct FooI2<'a>(&'static u8, std::marker::PhantomData<&'a ()>);
impl<'a> Foo for FooI2<'a> {
    type Bar = &'static u8;
    fn bar(&self) -> Self::Bar {
        self.0
    }
}

Would this work for you?

struct Baz<V, Bar>
where
    V: for<'a> ForLifetime<Of<'a>: Foo<Bar = Bar>>,
{
    bar: Bar,
    _pd: PhantomData<V>,
}

(There may still be some "lending function has to be 'static" GAT limitations, I didn't play with it.)

Maybe you don't need the PhantomData either. This seems to work as well:

struct Baz<V, Bar>
where
    V: for<'a> ForLifetime<Of<'a>: Foo<Bar = Bar>>,
{
    bar: <<V as ForLifetime>::Of<'static> as Foo>::Bar,
}
1 Like

In one of my crates, I used a helper trait to accomplish what I believe is similar to your goal:

pub trait UnvaryingFamily<'lower, Upper: ?Sized>:
    LifetimeFamily<'lower, Upper>
        + for<'varying> WithLifetime<'varying, 'lower, Upper, Is = Self::WithAnyLifetime>
{
    type WithAnyLifetime: ?Sized;
}

impl<'lower, Upper, T, U> UnvaryingFamily<'lower, Upper> for T
where
    Upper: ?Sized,
    T: ?Sized
        + LifetimeFamily<'lower, Upper>
        + for<'varying> WithLifetime<'varying, 'lower, Upper, Is = U>,
    U: ?Sized,
{
    type WithAnyLifetime = U;
}

I was pleasantly shocked to see that U isn't considered unconstrained, and that everything just worked with that helper trait (at least for my use case).

1 Like