chain_link/
lib.rs

1use sealed::Len;
2use seq_macro::seq;
3
4/// WIP I'm stuck between requiring `Length` trait and eliminating it
5///     If it's kept, it ensures the user cannot implement Chain past its length
6///         Which is a good guardrail, since it ensures the user always knows the chain length
7///     However it makes the API have more boilerplate
8///     But most importantly, it prevents us from cascading at custom index ranges
9///         And that's a feature that should be added in the future
10/// 
11/// WIP This is technically portable but requires some boilerplate on a newtype from the user
12///
13/// WIP currently structs are restricted to one type of chain impl
14///      so you can't end up with mulitple types of cascades on the same type
15///      this is desireable to reduce verbosity (which there is already too much of IMO)
16///      this can be simplified by supporting sequences:
17/// 
18/// WIP often we don't want to be able to transform inputs -> outputs during the chain
19///     it would be helpful for the user to be able to implement a Sequence<N> wrapper
20///     this would wrap the Chain<N> implementation, forcing its `In` and `Out` to be the same value
21const _WIP: () = ();
22
23/// Require all Length::Len types to be `L<const N: usize>` so that the InRange traits can be
24/// implemented with the same marker type. This is a workaround to the rust compiler blindspot
25/// which doesn't recognize certain non-overlapping trait impls and throws a compiler error.
26/// 
27/// Fails:    `impl<T: Length<Len = L<N>>> InRange<I>         for T {}`
28/// Fails:    `impl<T: Length>             InRange<I, T::Len> for T {}`
29/// Succeeds: `impl<T: Length<Len = L<N>>> InRange<I, L<N>>   for T {}`
30/// 
31/// Even though there's no way anything can impl Len more than once.
32mod sealed {
33    pub trait Len {
34        const LEN: usize;
35    }
36}
37
38// TODO I really hate this L<N> requirement, but we seem to need it to get around rust's
39//      compiler bug where non-overlapping trait impls are detected as overlapping, when
40//      using associated type equality as an impl condition
41// IDEA but maybe we can use it as the index into a compile-time indexing library?
42pub struct L<const N: usize>;
43impl<const N: usize> Len for L<N> {
44    const LEN: usize = N;
45}
46
47pub trait Length {
48    type Len: sealed::Len;
49
50    fn len() -> usize {
51        <Self::Len as Len>::LEN
52    }
53}
54
55pub trait InRange<const N: usize, L>: Length {}
56
57pub trait Chain<const N: usize>
58where
59    Self: InRange<N, <Self as Length>::Len>,
60{
61    type In<'a>;
62    type Out<'a>;
63
64    fn chain(input: Self::In<'_>) -> Self::Out<'_>;
65}
66
67pub trait Link<const N: usize> {
68    type In<'a>;
69    type Out<'a>;
70
71    fn link<'a>(input: Self::In<'a>) -> Self::Out<'a>;
72}
73
74impl<T: Chain<0>> Link<1> for T {
75    type In<'a> = <T as Chain<0>>::In<'a>;
76    type Out<'a> = <T as Chain<0>>::Out<'a>;
77
78    fn link(input: Self::In<'_>) -> Self::Out<'_> {
79        return <T as Chain<0>>::chain(input);
80    } 
81}
82
83// TODO currently an annoying limitation is the hardcoded limit to how many things can be chained
84//      realistically it's not such a problem, since nobody's gonna implement more than 32 chains manually
85//      but if they do it via macro, it could become a limitation, but this is the best way I found so far
86
87// add in-range marker trait impls for anything with up to length 32 (0..31 addressable)
88seq!(N in 1..=32 {
89    seq!(I in 0..N {
90        impl<T: Length<Len = L<N>>> InRange<I, L<N>> for T {}
91    });
92});
93
94// type gymnastics so that the input of the next link is the output of the previous one
95seq!(N in 2..=32 {
96    impl<T> Link<N> for T
97    where
98        T: Chain<0>,
99        for<'a> T: Link<{N - 1}, In<'a> = <T as Chain<0>>::In<'a>>,
100        for<'a> T: Chain<{N - 1}, In<'a> = <T as Link<{N - 1}>>::Out<'a>>,
101    {
102        type In<'a> = <T as Chain<0>>::In<'a>;
103        type Out<'a> = <T as Chain<{N - 1}>>::Out<'a>;
104
105        fn link(input: Self::In<'_>) -> Self::Out<'_> {
106            let out = <T as Link<{N - 1}>>::link(input);
107            return <T as Chain<{N - 1}>>::chain(out);
108        }
109    }
110});
111
112// support chaining from 0..N for any T: Length<Len = L<N>> that has a Link at `N - 1`
113
114pub trait Cascade {
115    type In<'a>;
116    type Out<'a>;
117
118    fn cascade(input: Self::In<'_>) -> Self::Out<'_>;
119}
120
121impl<const N: usize, T: Link<N> + Length<Len = L<N>>> Cascade for T {
122    type In<'a> = T::In<'a>;
123    type Out<'a> = T::Out<'a>;
124
125    fn cascade(input: Self::In<'_>) -> Self::Out<'_> {
126        return <T as Link::<N>>::link(input);
127    }
128}