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}