generic_upper_bound/
lib.rs

1#![no_std]
2// reason: flagged in macro generated code
3#![allow(clippy::absurd_extreme_comparisons)]
4#![allow(unused_comparisons)]
5
6//! This crate allows performing const calculations with the help of a generic const `usize`
7//! that is a reasonable upper bound of some desired associated const `usize`.
8//!
9//! The API of this crate is structed as follows:
10//! - [`AcceptUpperBound`] is the heart of this crate. Implementors use it to specify which
11//!   generic const they want to be passed to them and what to do with any given upper bound for it.
12//!   It can be implemented conveniently using [`impl_accept_upper_bound!`].
13//! - [`eval_with_upper_bound`] is used to get the result of evaluating an upper bound acceptor
14//!   with the best-effort upper bound that this crate can offer.
15//!
16//! While you cannot use this to write a function with a signature that returns e.g. `[T; M + N]`
17//! with generic `M` and `N`, you can use it to temporarily get an array of size `M + N`, use it
18//! to do something useful, then return the result of that computation.
19//! For example, you can concatenate two strings at compile time, even if their value is dependent
20//! on generic parameters:
21//! ```
22//! use generic_upper_bound as gub;
23//! pub trait MyTrait {
24//!     const SOME_STR: &'static str;
25//! }
26//! struct Concat<A, B>(A, B);
27//! gub::impl_accept_upper_bound! {
28//!     impl{A: MyTrait, B: MyTrait} Concat<A, B>;
29//!
30//!     const DESIRED_GENERIC: usize = A::SOME_STR.len() + B::SOME_STR.len();
31//!
32//!     const EVAL<const UPPER: usize>: &'static [u8] = &{
33//!         let l = A::SOME_STR.as_bytes();
34//!         let r = B::SOME_STR.as_bytes();
35//!         let mut out = [0; UPPER];
36//!         let mut off = 0;
37//!         // after 1.86, use split_at_mut and copy_from_slice
38//!         let mut i = 0;
39//!         while i < l.len() {
40//!             out[off] = l[i];
41//!             off += 1;
42//!             i += 1;
43//!         }
44//!         i = 0;
45//!         while i < r.len() {
46//!             out[off] = r[i];
47//!             off += 1;
48//!             i += 1;
49//!         }
50//!         out
51//!     };
52//! }
53//! impl<A: MyTrait, B: MyTrait> MyTrait for (A, B) {
54//!     // evaluate the upper bound acceptor, trim trailing nul bytes
55//!     // and convert to string
56//!     const SOME_STR: &'static str = match core::str::from_utf8(
57//!         gub::eval_with_upper_bound::<Concat<A, B>>()
58//!             .split_at(gub::desired_generic::<Concat<A, B>>())
59//!             .0,
60//!     ) {
61//!         Ok(s) => s,
62//!         _ => unreachable!(),
63//!     };
64//! }
65//! impl MyTrait for () {
66//!     const SOME_STR: &'static str = "ABC";
67//! }
68//! impl MyTrait for i32 {
69//!     const SOME_STR: &'static str = "123";
70//! }
71//! let concatenated: &'static str = <((), i32)>::SOME_STR;
72//! assert_eq!(concatenated, "ABC123");
73//! ```
74//! Note that this example can be generalized and optimized. For instance, it is possible to accept
75//! any `&'a [&'b str]` as input and this will also be more efficient (most of the time)
76//! due to the overhead from the inexact upper bound used for each concatenation (which will
77//! likely affect the final binary size).
78//!
79//! # MSRV
80//! The MSRV is 1.78. This is to allow this crate to be used as a workaround for the breaking change
81//! to const promotion that was introduced by that version.
82
83/// A trait for a type that holds a value.
84pub trait Const {
85    /// The type of the const
86    type Type;
87    /// The implementation of the value of the const. Use [`const_value`] for accessing the value.
88    const VALUE: Self::Type;
89}
90
91/// Alias for [`Const::Type`].
92pub type TypeOf<C> = <C as Const>::Type;
93
94/// Alias for [`Const::VALUE`]. Prefer this over accessing `VALUE` directly.
95///
96/// Using the associated constant through this function rather than directly causes it to only be
97/// evaluated when the branch that it is used in is actually executed, assuming that the execution
98/// happens at compile time (i.e. this does not apply to usage in regular `fn`s).
99/// This means that it may improve compile times, avoid errors for recursive consts and avoid evaluating
100/// panics.
101///
102/// For example:
103/// ```
104/// # use generic_upper_bound::*;
105/// struct Fallible;
106/// impl Const for Fallible {
107///     type Type = ();
108///     const VALUE: Self::Type = panic!();
109/// }
110/// const _: () = if false { const_value::<Fallible>() };  // this compiles
111/// ```
112/// ```compile_fail
113/// # use generic_upper_bound::*;
114/// # struct Fallible;
115/// # impl Const for Fallible {
116/// #     type Type = ();
117/// #     const VALUE: Self::Type = panic!();
118/// # }
119/// const _: () = if false { Fallible::VALUE }; // this gives a compile error
120/// ```
121pub const fn const_value<C: Const + ?Sized>() -> C::Type {
122    C::VALUE
123}
124
125/// Allows implementing a callback pattern that accepts an upper bound for a desired generic const
126/// parameter.
127///
128/// The trait works by allowing implementors to specify, for each upper bound, a value that is
129/// computed from that upper bound. This value is represented as a generic associated type with
130/// bound [`Const`], as this is currently the only way to implement a `const` item with generics
131/// in a trait.
132///
133/// When passed to [`eval_with_upper_bound`], [`Eval::<UPPER>::VALUE`](Const::VALUE) will be evaluated
134/// with a parameter `UPPER` that satisfies `DESIRED_GENERIC <= UPPER < 2 * DESIRED_GENERIC`.
135pub trait AcceptUpperBound {
136    /// The output type of the evaluation.
137    type Output;
138
139    /// The desired value and lower bound that the implementor wants to be passed to [`Self::Eval`].
140    const DESIRED_GENERIC: usize;
141
142    /// Evals the constant by mapping a generic parameter that is at least the desired value
143    /// to the output value. `const_value::<Eval<UPPER>>()` should be indistinguishable for
144    /// all *possible* generic parameters passed to this.
145    type Eval<const UPPER: usize>: Const<Type = Self::Output>;
146}
147
148struct Impl<F>(F);
149
150mod implementation;
151
152/// Returns [`F::DESIRED_GENERIC`](AcceptUpperBound::DESIRED_GENERIC).
153pub const fn desired_generic<F: AcceptUpperBound>() -> usize {
154    Impl::<F>::DESIRED
155}
156
157/// Returns the parameter that [`eval_with_upper_bound`] passes to [`F::Eval`](AcceptUpperBound::Eval).
158pub const fn get_upper_bound<F: AcceptUpperBound>() -> usize {
159    Impl::<F>::ACTUAL
160}
161
162/// Evaluates [`AcceptUpperBound`].
163///
164/// In the language of `generic_const_exprs`, this function returns
165/// `const_value::<F::Eval<{ get_upper_bound::<F>() }>>()`
166pub const fn eval_with_upper_bound<F: AcceptUpperBound>() -> F::Output {
167    Impl::<F>::EVAL
168}
169
170/// Implements [`AcceptUpperBound`] by generating a hidden [`Const`] implementor.
171///
172/// Generic parameters are passed in braces (`{...}`) after `impl` and cannot have a trailing
173/// comma. Where bounds are optionally passed in braces after the implementing type.
174///
175/// The example from the [crate level documentation](crate) can be written manually like this:
176/// ```
177/// use generic_upper_bound as gub;
178/// pub trait MyTrait {
179///     const SOME_STR: &'static str;
180/// }
181/// impl<A: MyTrait, B: MyTrait> MyTrait for (A, B) {
182///     const SOME_STR: &'static str = match core::str::from_utf8(
183///         gub::eval_with_upper_bound::<Concat<A, B>>()
184///             .split_at(gub::desired_generic::<Concat<A, B>>())
185///             .0,
186///     ) {
187///         Ok(s) => s,
188///         _ => unreachable!(),
189///     };
190/// }
191///
192/// struct Concat<A, B>(A, B);
193/// impl<A: MyTrait, B: MyTrait> gub::AcceptUpperBound for Concat<A, B> {
194///     type Output = &'static [u8];
195///     // Want to be passed at least the total length of the strings
196///     const DESIRED_GENERIC: usize = A::SOME_STR.len() + B::SOME_STR.len();
197///     // Decide on what to do with each generic const
198///     type Eval<const UPPER: usize> = ConcatImpl<A, B, UPPER>;
199/// }
200/// struct ConcatImpl<A, B, const N: usize>(A, B);
201/// impl<A: MyTrait, B: MyTrait, const N: usize> gub::Const for ConcatImpl<A, B, N> {
202///     type Type = &'static [u8];
203///     const VALUE: Self::Type = panic!("...");
204/// }
205/// ```
206#[macro_export]
207macro_rules! impl_accept_upper_bound {
208    {
209        $(#[$meta:meta])*
210        impl{$($params:tt)*} $Self:ty $({ $($where_bounds:tt)* })?;
211
212        const DESIRED_GENERIC: $usize_d:ty = $DESIRED_GENERIC:expr;
213        const EVAL<const $UPPER:ident: $usize_e:ty>: $Output:ty = $EVAL:expr;
214
215    } => {
216        const _: () = {
217            pub struct __Eval<__Eval, const $UPPER: $usize_e>(__Eval);
218            impl<$($params)*, const $UPPER: $usize_e> $crate::Const for __Eval<$Self, $UPPER> $($($where_bounds)*)? {
219                type Type = $Output;
220                const VALUE: Self::Type = $EVAL;
221            }
222            $(#[$meta])*
223            impl<$($params)*> $crate::AcceptUpperBound for $Self $($($where_bounds)*)? {
224                type Output = $Output;
225                const DESIRED_GENERIC: $usize_d = $DESIRED_GENERIC;
226                type Eval<const $UPPER: $usize_e> = __Eval<Self, $UPPER>;
227            }
228        };
229    };
230}