chain_link 0.1.2

Micro library with a util for chaining operations on a single type.
Documentation
use sealed::Len;
use seq_macro::seq;

/// TODO this isn't very portable.
///      Most of the time you want to define your own custom per-index impl of some specific trait.
///      And then cascade them by implementing Chain<N> for T: MyTrait<N>, but rust doesn't let you do this.
///      The best pattern is honestly to just copy this code locally and work with it that way as a workaround.
///      But I'd like to figure out a portable solution that gives you that option.
/// TODO currently structs are restricted to one type of chain impl
///      so you can't end up with mulitple types of cascades on the same type
///      this is desireable to reduce verbosity (which there is already too much of IMO)
///      this can be simplified by supporting sequences:
/// TODO often we don't want to be able to transform inputs -> outputs during the chain
///      it would be helpful for the user to be able to implement a Sequence<N> wrapper
///      this would wrap the Chain<N> implementation, forcing its `In` and `Out` to be the same value
const _TODO: () = ();

/// Require all Length::Len types to be `L<const N: usize>` so that the InRange traits can be
/// implemented with the same marker type. This is a workaround to the rust compiler blindspot
/// which doesn't recognize certain non-overlapping trait impls and throws a compiler error.
/// 
/// Fails:    `impl<T: Length<Len = L<N>>> InRange<I>         for T {}`
/// Fails:    `impl<T: Length>             InRange<I, T::Len> for T {}`
/// Succeeds: `impl<T: Length<Len = L<N>>> InRange<I, L<N>>   for T {}`
/// 
/// Even though there's no way anything can impl Len more than once.
mod sealed {
    pub trait Len {
        const LEN: usize;
    }
}

// TODO I really hate this L<N> requirement, but we seem to need it to get around rust's
//      compiler bug where non-overlapping trait impls are detected as overlapping, when
//      using associated type equality as an impl condition
pub struct L<const N: usize>;
impl<const N: usize> Len for L<N> {
    const LEN: usize = N;
}

pub trait Length {
    type Len: sealed::Len;

    fn len() -> usize {
        <Self::Len as Len>::LEN
    }
}

pub trait InRange<const N: usize, L>: Length {}

pub trait Chain<const N: usize>
where
    Self: Length,
    Self: InRange<N, <Self as Length>::Len>,
{
    type In<'a>;
    type Out<'a>;

    fn chain(input: Self::In<'_>) -> Self::Out<'_>;
}

pub trait Link<const N: usize> {
    type In<'a>;
    type Out<'a>;

    fn link<'a>(input: Self::In<'a>) -> Self::Out<'a>;
}

impl<T: Chain<0>> Link<1> for T {
    type In<'a> = <T as Chain<0>>::In<'a>;
    type Out<'a> = <T as Chain<0>>::Out<'a>;

    fn link(input: Self::In<'_>) -> Self::Out<'_> {
        return <T as Chain<0>>::chain(input);
    } 
}

// TODO currently an annoying limitation is the hardcoded limit to how many things can be chained
//      realistically it's not such a problem, since nobody's gonna implement more than 32 chains manually
//      but if they do it via macro, it could become a limitation, but this is the best way I found so far

// add in-range marker trait impls for anything with up to length 32 (0..31 addressable)
seq!(N in 1..=32 {
    seq!(I in 0..N {
        impl<T: Length<Len = L<N>>> InRange<I, L<N>> for T {}
    });
});

// type gymnastics so that the input of the next link is the output of the previous one
seq!(N in 2..=32 {
    impl<T> Link<N> for T
    where
        T: Chain<0>,
        for<'a> T: Link<{N - 1}, In<'a> = <T as Chain<0>>::In<'a>>,
        for<'a> T: Chain<{N - 1}, In<'a> = <T as Link<{N - 1}>>::Out<'a>>,
    {
        type In<'a> = <T as Chain<0>>::In<'a>;
        type Out<'a> = <T as Chain<{N - 1}>>::Out<'a>;

        fn link(input: Self::In<'_>) -> Self::Out<'_> {
            let out = <T as Link<{N - 1}>>::link(input);
            return <T as Chain<{N - 1}>>::chain(out);
        }
    }
});

// support chaining from 0..N for any T: Length<Len = L<N>> that has a Link at `N - 1`

pub trait Cascade {
    type In<'a>;
    type Out<'a>;

    fn cascade(input: Self::In<'_>) -> Self::Out<'_>;
}

impl<const N: usize, T: Link<N> + Length<Len = L<N>>> Cascade for T {
    type In<'a> = T::In<'a>;
    type Out<'a> = T::Out<'a>;

    fn cascade(input: Self::In<'_>) -> Self::Out<'_> {
        return <T as Link::<N>>::link(input);
    }
}