Nested Generics Inversion Magic Using GAT (or how to write a typed builder for typed linked list)

67 points znjw

Just discovered some really wierd pattern using GAT's type solver.

If you have a nested generic type B3<B2<B1<()>>>, and you associate each Bx with a generic type Cx, you can actually let the GAT type solver create a inverted C1<C2<C3<()>>> for you.

struct B1<BPrev> {
    previous: BPrev,
}
struct B2<BPrev> {
    previous: BPrev,
}
struct B3<BPrev> {
    previous: BPrev,
}

trait Builder {
    type R<RNext>;
    fn build<RNext>(&self, next: RNext) -> Self::R<RNext>;
}

impl Builder for () {
    type R<RNext> = RNext;
    fn build<RNext>(&self, next: RNext) -> Self::R<RNext> {
        next
    }
}

impl<BPrev: Builder> Builder for B1<BPrev> {
    type R<RNext> = BPrev::R<R1<RNext>>;
    fn build<RNext>(&self, next: RNext) -> Self::R<RNext> {
        self.previous.build(R1 { next })
    }
}

impl<BPrev: Builder> Builder for B2<BPrev> {
    type R<RNext> = BPrev::R<R2<RNext>>;
    fn build<RNext>(&self, next: RNext) -> Self::R<RNext> {
        self.previous.build(R2 { next })
    }
}

impl<BPrev: Builder> Builder for B3<BPrev> {
    type R<RNext> = BPrev::R<R3<RNext>>;

    fn build<RNext>(&self, next: RNext) -> Self::R<RNext> {
        self.previous.build(R3 { next })
    }
}

struct R1<RNext> {
    next: RNext,
}
struct R2<RNext> {
    next: RNext,
}
struct R3<RNext> {
    next: RNext,
}

#[test]
fn test() {
    let b = ();
    let b = B1 { previous: b }; // B1<()>
    let b = B2 { previous: b }; // B2<B1<()>>
    let b = B3 { previous: b }; // B3<B2<B1<()>>>

    let r = b.build(()); // R1<R2<R3<()>>>

    fn type_of<T: 'static>(x: T) -> TypeId {
        TypeId::of::<T>()
    }
    assert_eq!(type_of(b), TypeId::of::<B3<B2<B1<()>>>>());
    assert_eq!(type_of(r), TypeId::of::<R1<R2<R3<()>>>>());
}

I know GAT can do a lot of wierd stuff, but this thing...... Does it have a name or something?

One usage I think of is to create a typed builder for a typed linked list that has forward type order during building, Somthing like:

let builder = TypeLinkedListBuilder::new()
    .add::<T1>(t1)
    .add::<T2>(t2)
    .add::<T3>(t3); // Builder<T3, Builder<T2, Builder<T1, Builder<()>>>>
let list = builder.build(); // Node<T1, Node<T2, Node<T3, ()>>>>
let (t1, list) = list.pop(); // T1
let (t2, list) = list.pop(); // T2
let (t3, ()) = list.pop();  // T3