Trouble expressing chain of GATs

Hi all,

I'm writing a pipeline trait like this:

pub trait Transform<I> {
    type Output<'o>;

    fn transform(&self, input: I, output: impl for<'o> FnMut(Self::Output<'o>));
}

Which works out pretty nicely to build a chain of transformers. Full code in playground

Summary
/// Transformer that yields the given items
pub struct Yield<T> {
    pub items: Vec<T>,
}

impl<T: Clone> Transform<()> for Yield<T> {
    type Output<'o> = T;

    fn transform(&self, _input: (), output: impl for<'o> FnMut(Self::Output<'o>)) {
        self.items.iter().cloned().for_each(output)
    }
}

/// Transformer that splits the input by whitespace
pub struct Words {}

impl<I: AsRef<str>> Transform<I> for Words {
    type Output<'o> = &'o str;

    fn transform(&self, input: I, output: impl for<'o> FnMut(Self::Output<'o>)) {
        input.as_ref().split_ascii_whitespace().for_each(output)
    }
}

/// Transformer that repeats every input a number of times
pub struct Repeat {
    pub times: usize,
}

impl<I: Clone> Transform<I> for Repeat {
    type Output<'o> = I;

    fn transform(&self, input: I, mut output: impl for<'o> FnMut(Self::Output<'o>)) {
        for _ in 0..self.times {
            (output)(input.clone())
        }
    }
}

/// Transformer that prints the input to stdout
pub struct Print {}

impl<I: core::fmt::Display> Transform<I> for Print {
    type Output<'o> = ();

    fn transform(&self, input: I, mut output: impl for<'o> FnMut(Self::Output<'o>)) {
        println!("{}", input);
        (output)(()); // just signal
    }
}

Because I can now run a pipeline like this

let start = Yield {
    items: vec![String::from("hello world"), String::from("cookie dough")],
}; // just yield these inputs
let words = Words {}; // split strings by whitespace
let print = Print {}; // print items

let mut count = 0;
let mut terminating = |_| count += 1;

// closure needs type annotation in order to compile,
// https://users.rust-lang.org/t/implementation-of-fnonce-is-not-general-enough/68294/3
let mut chain = |w: &str| print.transform(w, &mut terminating);
let mut chain = |s| words.transform(s, &mut chain);
start.transform((), |s| chain(s));

assert_eq!(count, 4);

To write these chains I introduced:

/// Chain of two Transformers
pub struct Chain<T, U, I> {
    t1: T,
    t2: U,
    marker_i: PhantomData<fn() -> I>,
}

impl<I, M, T: for<'i> Transform<I, Output<'i> = M>, U: Transform<M>> Chain<T, U, I> {
    pub fn new(t1: T, t2: U) -> Self {
        Self {
            t1,
            t2,
            marker_i: PhantomData,
        }
    }
}

impl<I, M, T: for<'i> Transform<I, Output<'i> = M>, U: Transform<M>> Transform<I>
    for Chain<T, U, I>
{
    type Output<'o> = U::Output<'o>;

    fn transform(&self, input: I, mut output: impl for<'o> FnMut(Self::Output<'o>)) {
        let f = |u: T::Output<'_>| self.t2.transform(u, &mut output);
        self.t1.transform(input, f);
    }
}

Which works great for owned intermediate values

let start = Yield {
    items: vec![String::from("hello world"), String::from("cookie dough")],
}; // just yield these inputs
let repeat = Repeat { times: 2 }; // repeat every input twice
let print = Print {}; // print items

let chain = Chain::new(start, repeat);
let chain = Chain::new(chain, print);

let mut count = 0;
chain.transform((), |_| count += 1);
assert_eq!(count, 4);

But fails to compile for borrowed intermediate values

let start = Yield {
    items: vec![String::from("hello world"), String::from("cookie dough")],
}; // just yield these inputs
let words = Words {}; // split each input by whitespace
let print = Print {}; // print items

let chain = Chain::new(words, print);
let chain = Chain::new(start, chain);

let mut count = 0;
chain.transform((), |_| count += 1);
assert_eq!(count, 4);

with

error[E0599]: the method transformexists for structChain<Yield, Chain<Words, Print, String>, ()>, but its trait bounds were not satisfied

I could really use some help here. Are the Chain trait bounds correct?

You're constraining all T::Output<'i> to be equal to a single type M regardless of what 'i is. It can't vary with 'i and therefore can’t actually borrow anything.

2 Likes

There's another problem too: if you rewrite the bounds like so

impl<T, U, I> Transform<I> for Chain<T, U, I>
where
    T: for<'i> Transform<I>,
    U: for<'i> Transform<T::Output<'i>>,

Then there's no reason U's implementation couldn't have looked like so

impl<'a> Transform<TsOutput<'a>> for U {
    type Output<'b> = (TyX<'a>, TyY<'b>);

And you don't have a way to make those lifetimes independent here:

    // Lifetime isn't in scope                   vv
    type Output<'o> = < U as Transform<T::Output<'i>> >::Output<'o>;
//  type Output<'b> = (TyX<'a>, TyY<'b>);
    // Same problem        ^^

So some larger change in approach is probably required.

Here's one way where I represent

  • T: 'static with impl Matter for Owned<T>
  • &U where U: 'static with impl Matter for Ref<U>

And in general single-lifetime type constructors can be represented via some marker type implementation of Matter. Limiting to a single non-'static lifetime and consolidating them all in one trait avoids the "multiple lifetime dimensions in nested GATs" complication.

But, as you can see, it completely wrecks inference on most method calls. I outlined what I believe is the reason in the comments. The TransferTo helper trait offers a slightly better workaround for specifying the input Matter implementation without having to use a complete <Ty as Transform<Ref<_>>::transform implementation path (as seen in chain_ref_raw).

Thanks for the elaborate replies. Quite a lot to unpack here.

Your playground solution is pretty sweet @quinedot but it does indeed complicate all the Transform implementations a bit. I need some more time to fully grasp the solution!

Regarding @kpreid

You're constraining all T::Output<'i> to be equal to a single type M regardless of what 'i is. It can't vary with 'i and therefore can’t actually borrow anything.

I have also tried to make the input parameter a GAT:

pub trait Transform {
    type Input<'i>;
    type Output<'o>;

    fn transform<'i>(&self, input: Self::Input<'i>, output: impl for<'o> FnMut(Self::Output<'o>));
}

pub struct Chain<T, U> {
    t1: T,
    t2: U,
}

impl<T: Transform, U: for<'a> Transform<Input<'a> = T::Output<'a>>> Chain<T, U> {
    pub fn new(t1: T, t2: U) -> Self {
        Self { t1, t2 }
    }
}

impl<T: for<'a> Transform<Output<'a> = U::Input<'a>>, U: Transform> Transform for Chain<T, U> {
    type Input<'i> = T::Input<'i>;
    type Output<'o> = U::Output<'o>;

    fn transform<'i>(
        &self,
        input: Self::Input<'i>,
        mut output: impl for<'o> FnMut(Self::Output<'o>),
    ) {
        let f = |u: T::Output<'_>| self.t2.transform(u, &mut output);
        self.t1.transform(input, f);
    }
}

But it leads to a somewhat different error, but probably due to the same underlying issue?

full playground

error[E0599]: the method `transform` exists for struct `Chain<Words, Print<&str>>`, but its trait bounds were not satisfied
   --> src/lib.rs:160:15
    |
52  | pub struct Words { }
    | ---------------- doesn't satisfy `<Words as Transform>::Output<'a> = &str`
...
83  | pub struct Chain<T, U> {
    | ---------------------- method `transform` not found for this struct because it doesn't satisfy `Chain<Words, Print<&str>>: Transform`
...
160 |         chain.transform("abc aa", |_| count += 1);
    |               ^^^^^^^^^ method cannot be called on `Chain<Words, Print<&str>>` due to unsatisfied trait bounds
    |
note: trait bound `<Words as Transform>::Output<'a> = &str` was not satisfied
   --> src/lib.rs:94:27
    |
94  | impl<T: for<'a> Transform<Output<'a> = U::Input<'a>>, U: Transform> Transform for Chain<T, U> {
    |                           ^^^^^^^^^^^^^^^^^^^^^^^^^                 ---------     -----------
    |                           |
    |                           unsatisfied trait bound introduced here
    = help: items from traits can only be used if the trait is implemented and in scope
note: `Transform` defines an item `transform`, perhaps you need to implement it
   --> src/lib.rs:1:1
    |
1   | pub trait Transform {
    | ^^^^^^^^^^^^^^^^^^^

For more information about this error, try `rustc --explain E0599`.