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}