Skip to main content

metrique_core/
concat.rs

1//! Utilities for concatenating strings
2//!
3//! This exists because const generics is insufficiently useful. It should
4//! be greatly simplified once const generics is better.
5
6use std::{borrow::Cow, marker::PhantomData};
7
8use self::private::SealedMaybeConstStr;
9
10/// A trait representing a constant string lifted to a constant
11pub trait ConstStr {
12    /// The constant string value
13    const VAL: &'static str;
14}
15
16/// An empty constant string
17pub struct EmptyConstStr;
18
19impl ConstStr for EmptyConstStr {
20    const VAL: &'static str = "";
21}
22
23impl<T: ConstStr> MaybeConstStr for T {
24    const MAYBE_VAL: &'static str = Self::VAL;
25    const LEN: usize = const { Self::VAL.len() };
26    const HAVE_VAL: bool = true;
27    fn extend(into: &mut String) {
28        into.push_str(Self::VAL);
29    }
30}
31
32impl<T: ConstStr> SealedMaybeConstStr for T {}
33
34/// A trait representing a string that is possibly a constant
35///
36/// The associated constants are implementation details and
37/// not to be used.
38// The `extend` function will extend the content string (with a known length)
39// into an input buffer.
40//
41// If `HAVE_VAL` is true, then `MAYBE_VAL` contains the same value as the result of
42// extracting this by running:
43// ```
44// let mut buf = String::with_capacity(Self::LEN);
45// Self::extend(&mut buf);
46// &buf[..]
47// ```
48//
49// If `HAVE_VAL` is false, then `MAYBE_VAL` contains garbage.
50//
51// Ideally, const generics would be better and we would not need this hack.
52pub trait MaybeConstStr: SealedMaybeConstStr {
53    #[doc(hidden)]
54    const MAYBE_VAL: &'static str;
55    #[doc(hidden)]
56    const LEN: usize = 0;
57    #[doc(hidden)]
58    const HAVE_VAL: bool;
59    /// Extend the value into a given string
60    fn extend(into: &mut String);
61}
62
63mod private {
64    pub trait SealedMaybeConstStr {}
65}
66
67/// The concatenation of 2 constant strings
68pub struct Concatenated<S, T>(S, T);
69
70const fn concatenate_strings<const N: usize>(x: &[u8], y: &[u8]) -> [u8; N] {
71    let mut buf = [0; N];
72    if N != x.len() + y.len() {
73        return buf;
74    }
75    let mut i = 0;
76    while i < x.len() {
77        buf[i] = x[i];
78        i += 1;
79    }
80    let mut i = 0;
81    while i < y.len() {
82        buf[x.len() + i] = y[i];
83        i += 1;
84    }
85    buf
86}
87
88struct ConcatenatedLen<X: MaybeConstStr, Y: MaybeConstStr, const N: usize>(
89    X,
90    Y,
91    PhantomData<[u8; N]>,
92);
93impl<S: MaybeConstStr, T: MaybeConstStr, const N: usize> MaybeConstStr
94    for ConcatenatedLen<S, T, N>
95{
96    const MAYBE_VAL: &str = const {
97        let buf =
98            const { &concatenate_strings::<N>(S::MAYBE_VAL.as_bytes(), T::MAYBE_VAL.as_bytes()) };
99        match std::str::from_utf8(buf) {
100            Ok(res) => res,
101            Err(_) => panic!(),
102        }
103    };
104    const HAVE_VAL: bool = S::HAVE_VAL && T::HAVE_VAL;
105    const LEN: usize = S::LEN + T::LEN;
106    fn extend(into: &mut String) {
107        S::extend(into);
108        T::extend(into);
109    }
110}
111impl<S: MaybeConstStr, T: MaybeConstStr, const N: usize> SealedMaybeConstStr
112    for ConcatenatedLen<S, T, N>
113{
114}
115
116impl<S: MaybeConstStr, T: MaybeConstStr> MaybeConstStr for Concatenated<S, T> {
117    // For strings over length 100, `HAVE_VAL = false` so allocate. It is possible
118    // to change the 100 for some other value, you need to change the length of
119    // the match initializing MAYBE_VAL.
120    const HAVE_VAL: bool = S::HAVE_VAL && T::HAVE_VAL && (S::LEN + T::LEN) <= 100;
121    const LEN: usize = S::LEN + T::LEN;
122    fn extend(into: &mut String) {
123        S::extend(into);
124        T::extend(into);
125    }
126    // Hack since const generics are less ideal than we want.
127    const MAYBE_VAL: &str = match S::MAYBE_VAL.len() + T::MAYBE_VAL.len() {
128        0 => ConcatenatedLen::<S, T, 0>::MAYBE_VAL,
129        1 => ConcatenatedLen::<S, T, 1>::MAYBE_VAL,
130        2 => ConcatenatedLen::<S, T, 2>::MAYBE_VAL,
131        3 => ConcatenatedLen::<S, T, 3>::MAYBE_VAL,
132        4 => ConcatenatedLen::<S, T, 4>::MAYBE_VAL,
133        5 => ConcatenatedLen::<S, T, 5>::MAYBE_VAL,
134        6 => ConcatenatedLen::<S, T, 6>::MAYBE_VAL,
135        7 => ConcatenatedLen::<S, T, 7>::MAYBE_VAL,
136        8 => ConcatenatedLen::<S, T, 8>::MAYBE_VAL,
137        9 => ConcatenatedLen::<S, T, 9>::MAYBE_VAL,
138        10 => ConcatenatedLen::<S, T, 10>::MAYBE_VAL,
139        11 => ConcatenatedLen::<S, T, 11>::MAYBE_VAL,
140        12 => ConcatenatedLen::<S, T, 12>::MAYBE_VAL,
141        13 => ConcatenatedLen::<S, T, 13>::MAYBE_VAL,
142        14 => ConcatenatedLen::<S, T, 14>::MAYBE_VAL,
143        15 => ConcatenatedLen::<S, T, 15>::MAYBE_VAL,
144        16 => ConcatenatedLen::<S, T, 16>::MAYBE_VAL,
145        17 => ConcatenatedLen::<S, T, 17>::MAYBE_VAL,
146        18 => ConcatenatedLen::<S, T, 18>::MAYBE_VAL,
147        19 => ConcatenatedLen::<S, T, 19>::MAYBE_VAL,
148        20 => ConcatenatedLen::<S, T, 20>::MAYBE_VAL,
149        21 => ConcatenatedLen::<S, T, 21>::MAYBE_VAL,
150        22 => ConcatenatedLen::<S, T, 22>::MAYBE_VAL,
151        23 => ConcatenatedLen::<S, T, 23>::MAYBE_VAL,
152        24 => ConcatenatedLen::<S, T, 24>::MAYBE_VAL,
153        25 => ConcatenatedLen::<S, T, 25>::MAYBE_VAL,
154        26 => ConcatenatedLen::<S, T, 26>::MAYBE_VAL,
155        27 => ConcatenatedLen::<S, T, 27>::MAYBE_VAL,
156        28 => ConcatenatedLen::<S, T, 28>::MAYBE_VAL,
157        29 => ConcatenatedLen::<S, T, 29>::MAYBE_VAL,
158        30 => ConcatenatedLen::<S, T, 30>::MAYBE_VAL,
159        31 => ConcatenatedLen::<S, T, 31>::MAYBE_VAL,
160        32 => ConcatenatedLen::<S, T, 32>::MAYBE_VAL,
161        33 => ConcatenatedLen::<S, T, 33>::MAYBE_VAL,
162        34 => ConcatenatedLen::<S, T, 34>::MAYBE_VAL,
163        35 => ConcatenatedLen::<S, T, 35>::MAYBE_VAL,
164        36 => ConcatenatedLen::<S, T, 36>::MAYBE_VAL,
165        37 => ConcatenatedLen::<S, T, 37>::MAYBE_VAL,
166        38 => ConcatenatedLen::<S, T, 38>::MAYBE_VAL,
167        39 => ConcatenatedLen::<S, T, 39>::MAYBE_VAL,
168        40 => ConcatenatedLen::<S, T, 40>::MAYBE_VAL,
169        41 => ConcatenatedLen::<S, T, 41>::MAYBE_VAL,
170        42 => ConcatenatedLen::<S, T, 42>::MAYBE_VAL,
171        43 => ConcatenatedLen::<S, T, 43>::MAYBE_VAL,
172        44 => ConcatenatedLen::<S, T, 44>::MAYBE_VAL,
173        45 => ConcatenatedLen::<S, T, 45>::MAYBE_VAL,
174        46 => ConcatenatedLen::<S, T, 46>::MAYBE_VAL,
175        47 => ConcatenatedLen::<S, T, 47>::MAYBE_VAL,
176        48 => ConcatenatedLen::<S, T, 48>::MAYBE_VAL,
177        49 => ConcatenatedLen::<S, T, 49>::MAYBE_VAL,
178        50 => ConcatenatedLen::<S, T, 50>::MAYBE_VAL,
179        51 => ConcatenatedLen::<S, T, 51>::MAYBE_VAL,
180        52 => ConcatenatedLen::<S, T, 52>::MAYBE_VAL,
181        53 => ConcatenatedLen::<S, T, 53>::MAYBE_VAL,
182        54 => ConcatenatedLen::<S, T, 54>::MAYBE_VAL,
183        55 => ConcatenatedLen::<S, T, 55>::MAYBE_VAL,
184        56 => ConcatenatedLen::<S, T, 56>::MAYBE_VAL,
185        57 => ConcatenatedLen::<S, T, 57>::MAYBE_VAL,
186        58 => ConcatenatedLen::<S, T, 58>::MAYBE_VAL,
187        59 => ConcatenatedLen::<S, T, 59>::MAYBE_VAL,
188        60 => ConcatenatedLen::<S, T, 60>::MAYBE_VAL,
189        61 => ConcatenatedLen::<S, T, 61>::MAYBE_VAL,
190        62 => ConcatenatedLen::<S, T, 62>::MAYBE_VAL,
191        63 => ConcatenatedLen::<S, T, 63>::MAYBE_VAL,
192        64 => ConcatenatedLen::<S, T, 64>::MAYBE_VAL,
193        65 => ConcatenatedLen::<S, T, 65>::MAYBE_VAL,
194        66 => ConcatenatedLen::<S, T, 66>::MAYBE_VAL,
195        67 => ConcatenatedLen::<S, T, 67>::MAYBE_VAL,
196        68 => ConcatenatedLen::<S, T, 68>::MAYBE_VAL,
197        69 => ConcatenatedLen::<S, T, 69>::MAYBE_VAL,
198        70 => ConcatenatedLen::<S, T, 70>::MAYBE_VAL,
199        71 => ConcatenatedLen::<S, T, 71>::MAYBE_VAL,
200        72 => ConcatenatedLen::<S, T, 72>::MAYBE_VAL,
201        73 => ConcatenatedLen::<S, T, 73>::MAYBE_VAL,
202        74 => ConcatenatedLen::<S, T, 74>::MAYBE_VAL,
203        75 => ConcatenatedLen::<S, T, 75>::MAYBE_VAL,
204        76 => ConcatenatedLen::<S, T, 76>::MAYBE_VAL,
205        77 => ConcatenatedLen::<S, T, 77>::MAYBE_VAL,
206        78 => ConcatenatedLen::<S, T, 78>::MAYBE_VAL,
207        79 => ConcatenatedLen::<S, T, 79>::MAYBE_VAL,
208        80 => ConcatenatedLen::<S, T, 80>::MAYBE_VAL,
209        81 => ConcatenatedLen::<S, T, 81>::MAYBE_VAL,
210        82 => ConcatenatedLen::<S, T, 82>::MAYBE_VAL,
211        83 => ConcatenatedLen::<S, T, 83>::MAYBE_VAL,
212        84 => ConcatenatedLen::<S, T, 84>::MAYBE_VAL,
213        85 => ConcatenatedLen::<S, T, 85>::MAYBE_VAL,
214        86 => ConcatenatedLen::<S, T, 86>::MAYBE_VAL,
215        87 => ConcatenatedLen::<S, T, 87>::MAYBE_VAL,
216        88 => ConcatenatedLen::<S, T, 88>::MAYBE_VAL,
217        89 => ConcatenatedLen::<S, T, 89>::MAYBE_VAL,
218        90 => ConcatenatedLen::<S, T, 90>::MAYBE_VAL,
219        91 => ConcatenatedLen::<S, T, 91>::MAYBE_VAL,
220        92 => ConcatenatedLen::<S, T, 92>::MAYBE_VAL,
221        93 => ConcatenatedLen::<S, T, 93>::MAYBE_VAL,
222        94 => ConcatenatedLen::<S, T, 94>::MAYBE_VAL,
223        95 => ConcatenatedLen::<S, T, 95>::MAYBE_VAL,
224        96 => ConcatenatedLen::<S, T, 96>::MAYBE_VAL,
225        97 => ConcatenatedLen::<S, T, 97>::MAYBE_VAL,
226        98 => ConcatenatedLen::<S, T, 98>::MAYBE_VAL,
227        99 => ConcatenatedLen::<S, T, 99>::MAYBE_VAL,
228        100 => ConcatenatedLen::<S, T, 100>::MAYBE_VAL,
229        _ => "",
230    };
231}
232impl<S: MaybeConstStr, T: MaybeConstStr> SealedMaybeConstStr for Concatenated<S, T> {}
233
234/// Return the value of a given [MaybeConstStr]. If possible, will return
235/// the value without allocating. It might not be always possible due
236/// to const eval limitations.
237pub fn const_str_value<S: MaybeConstStr>() -> Cow<'static, str> {
238    if S::HAVE_VAL {
239        Cow::Borrowed(S::MAYBE_VAL)
240    } else {
241        let mut buf = String::with_capacity(S::LEN);
242        S::extend(&mut buf);
243        Cow::Owned(buf)
244    }
245}
246
247#[cfg(test)]
248mod test {
249    use std::borrow::Cow;
250
251    use crate::concat::{Concatenated, ConstStr, const_str_value};
252
253    struct ConstFoo;
254    impl ConstStr for ConstFoo {
255        const VAL: &str = "Foo_";
256    }
257
258    struct ConstBar;
259    impl ConstStr for ConstBar {
260        const VAL: &str = "Bar";
261    }
262
263    struct ConstMinus;
264    impl ConstStr for ConstMinus {
265        const VAL: &str = "-";
266    }
267
268    type MinusConcated<U, V> = Concatenated<U, Concatenated<ConstMinus, V>>;
269    type VL1 = MinusConcated<ConstFoo, ConstBar>;
270    type VL2 = MinusConcated<VL1, VL1>;
271    type VL3 = MinusConcated<VL2, VL2>;
272    type VL4 = MinusConcated<VL3, VL3>;
273    type VL5 = MinusConcated<VL4, VL4>;
274    type VL6 = MinusConcated<VL5, VL5>;
275
276    #[test]
277    fn main() {
278        assert_eq!(
279            const_str_value::<Concatenated<ConstFoo, ConstBar>>(),
280            "Foo_Bar"
281        );
282        let vl6 = const_str_value::<VL6>();
283        match vl6 {
284            Cow::Owned(a) => assert_eq!(
285                a,
286                "Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar"
287            ),
288            _ => panic!(),
289        };
290        let vl5 = const_str_value::<VL5>();
291        match vl5 {
292            Cow::Owned(a) => assert_eq!(
293                a,
294                "Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar"
295            ),
296            _ => panic!(),
297        };
298        let vl4 = const_str_value::<VL4>();
299        match vl4 {
300            Cow::Borrowed(a) => assert_eq!(
301                a,
302                "Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar-Foo_-Bar"
303            ),
304            _ => panic!(),
305        };
306    }
307}