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#![allow(rustdoc::redundant_explicit_links)]
6
7//! This crate allows performing const calculations with the help of a generic const `usize`
8//! that is a reasonable upper bound of some desired associated const `usize`.
9//!
10//! The API of this crate is structed as follows:
11//! - [`AcceptUpperBound`](crate::AcceptUpperBound) is the heart of this crate. Implementors use it to specify which
12//!   generic const they want to be passed to them and what to do with any given upper bound for it.
13//!   It can be implemented conveniently using [`impl_accept_upper_bound!`](crate::impl_accept_upper_bound).
14//! - [`eval_with_upper_bound`](crate::eval_with_upper_bound) is used to get the result of evaluating an upper bound acceptor
15//!   with the best-effort upper bound that this crate can offer.
16//!
17//! While you cannot use this to write a function with a signature that returns e.g. `[T; M + N]`
18//! with generic `M` and `N`, you can use it to temporarily get an array of size `M + N`, use it
19//! to do something useful, then return the result of that computation.
20//! For example, you can concatenate two strings at compile time, even if their value is dependent
21//! on generic parameters:
22//! ```
23//! use generic_upper_bound as gub;
24//! pub trait MyTrait {
25//!     const SOME_STR: &'static str;
26//! }
27//! struct Concat<A, B>(A, B);
28//! gub::impl_accept_upper_bound! {
29//!     impl{A: MyTrait, B: MyTrait} Concat<A, B>;
30//!
31//!     const DESIRED_GENERIC: usize = A::SOME_STR.len() + B::SOME_STR.len();
32//!
33//!     const EVAL<const UPPER: usize>: &'static [u8] = &{
34//!         let l = A::SOME_STR.as_bytes();
35//!         let r = B::SOME_STR.as_bytes();
36//!         let mut out = [0; UPPER];
37//!         let mut off = 0;
38//!         // after 1.86, use split_at_mut and copy_from_slice
39//!         let mut i = 0;
40//!         while i < l.len() {
41//!             out[off] = l[i];
42//!             off += 1;
43//!             i += 1;
44//!         }
45//!         i = 0;
46//!         while i < r.len() {
47//!             out[off] = r[i];
48//!             off += 1;
49//!             i += 1;
50//!         }
51//!         out
52//!     };
53//! }
54//! impl<A: MyTrait, B: MyTrait> MyTrait for (A, B) {
55//!     // evaluate the upper bound acceptor, trim trailing nul bytes
56//!     // and convert to string
57//!     const SOME_STR: &'static str = match core::str::from_utf8(
58//!         gub::eval_with_upper_bound::<Concat<A, B>>()
59//!             .split_at(gub::desired_generic::<Concat<A, B>>())
60//!             .0,
61//!     ) {
62//!         Ok(s) => s,
63//!         _ => unreachable!(),
64//!     };
65//! }
66//! impl MyTrait for () {
67//!     const SOME_STR: &'static str = "ABC";
68//! }
69//! impl MyTrait for i32 { 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//! See the [`const-util`](https://docs.rs/const-util/latest/const_util/) crate for an
80//! implementation of this.
81//!
82//! # MSRV
83//! The MSRV is 1.78. This is to allow this crate to be used as a workaround for the breaking change
84//! to const promotion that was introduced by that version.
85
86pub extern crate type_const;
87pub use type_const::{value_of as const_value, Const, TypeOf};
88
89/// Allows implementing a callback pattern that accepts an upper bound for a desired generic const
90/// parameter.
91///
92/// The trait works by allowing implementors to specify, for each upper bound, a value that is
93/// computed from that upper bound. This value is represented as a generic associated type with
94/// bound [`Const`], as this is currently the only way to implement a `const` item with generics
95/// in a trait.
96///
97/// When passed to [`eval_with_upper_bound`], [`Eval::<UPPER>::VALUE`](Const::VALUE) will be evaluated
98/// with a parameter `UPPER` that satisfies `DESIRED_GENERIC <= UPPER < 2 * DESIRED_GENERIC`.
99pub trait AcceptUpperBound {
100    /// The output type of the evaluation.
101    type Output;
102
103    /// The desired value and lower bound that the implementor wants to be passed to [`Self::Eval`].
104    const DESIRED_GENERIC: usize;
105
106    /// Evals the constant by mapping a generic parameter that is at least the desired value
107    /// to the output value. `const_value::<Eval<UPPER>>()` should be indistinguishable for
108    /// all *possible* generic parameters passed to this.
109    type Eval<const UPPER: usize>: Const<Type = Self::Output>;
110}
111
112struct Impl<A>(A);
113
114mod implementation;
115
116/// Returns [`AcceptUpperBound::DESIRED_GENERIC`].
117pub const fn desired_generic<A: AcceptUpperBound>() -> usize {
118    Impl::<A>::DESIRED
119}
120
121/// Returns the parameter that [`eval_with_upper_bound`] passes to [`AcceptUpperBound::Eval`].
122pub const fn get_upper_bound<A: AcceptUpperBound>() -> usize {
123    Impl::<A>::ACTUAL
124}
125
126/// Evaluates [`AcceptUpperBound`].
127///
128/// In the language of `generic_const_exprs`, this function returns
129/// `const_value::<F::Eval<{ get_upper_bound::<F>() }>>()`
130pub const fn eval_with_upper_bound<A: AcceptUpperBound>() -> A::Output {
131    Impl::<A>::EVAL
132}
133
134/// Implements [`AcceptUpperBound`] by generating a hidden [`Const`] implementor.
135///
136/// Generic parameters are passed in braces (`{...}`) after `impl` and cannot have a trailing
137/// comma. Where bounds are optionally passed in braces after the implementing type.
138///
139/// The example from the [crate level documentation](crate) can be written manually like this:
140/// ```
141/// use generic_upper_bound as gub;
142/// pub trait MyTrait {
143///     const SOME_STR: &'static str;
144/// }
145/// impl<A: MyTrait, B: MyTrait> MyTrait for (A, B) {
146///     const SOME_STR: &'static str = match core::str::from_utf8(
147///         gub::eval_with_upper_bound::<Concat<A, B>>()
148///             .split_at(gub::desired_generic::<Concat<A, B>>())
149///             .0,
150///     ) {
151///         Ok(s) => s,
152///         _ => unreachable!(),
153///     };
154/// }
155///
156/// struct Concat<A, B>(A, B);
157/// impl<A: MyTrait, B: MyTrait> gub::AcceptUpperBound for Concat<A, B> {
158///     type Output = &'static [u8];
159///     // Want to be passed at least the total length of the strings
160///     const DESIRED_GENERIC: usize = A::SOME_STR.len() + B::SOME_STR.len();
161///     // Decide on what to do with each generic const
162///     type Eval<const UPPER: usize> = ConcatImpl<A, B, UPPER>;
163/// }
164/// struct ConcatImpl<A, B, const N: usize>(A, B);
165/// impl<A: MyTrait, B: MyTrait, const N: usize> gub::Const for ConcatImpl<A, B, N> {
166///     type Type = &'static [u8];
167///     const VALUE: Self::Type = panic!("...");
168/// }
169/// ```
170#[macro_export]
171macro_rules! impl_accept_upper_bound {
172    {
173        $(#[$meta:meta])*
174        impl{$($params:tt)*} $Self:ty $({ $($where_bounds:tt)* })?;
175
176        const DESIRED_GENERIC: $usize_d:ty = $DESIRED_GENERIC:expr;
177        const EVAL<const $UPPER:ident: $usize_e:ty>: $Output:ty = $EVAL:expr;
178
179    } => {
180        const _: () = {
181            pub struct __Eval<__Eval, const $UPPER: $usize_e>(__Eval);
182            impl<$($params)*, const $UPPER: $usize_e> $crate::Const for __Eval<$Self, $UPPER> $($($where_bounds)*)? {
183                type Type = $Output;
184                const VALUE: Self::Type = $EVAL;
185            }
186            $(#[$meta])*
187            impl<$($params)*> $crate::AcceptUpperBound for $Self $($($where_bounds)*)? {
188                type Output = $Output;
189                const DESIRED_GENERIC: $usize_d = $DESIRED_GENERIC;
190                type Eval<const $UPPER: $usize_e> = __Eval<Self, $UPPER>;
191            }
192        };
193    };
194}