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//! Provides functionality to get a const generic `usize` that is that is a reasonable
7//! upper bound for a specified associated const `usize` for the purpose of intermediate
8//! const calculations, as a workaround for `generic_const_exprs`.
9//!
10//! The API of this crate is structed as follows:
11//! - [`AcceptUpperBound`] is the heart of this crate. Implementors use it to specify which
12//! generic const they want to be passed and what to do with any given upper bound for it.
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 paramters:
21//! ```
22//! use generic_upper_bound as gub;
23//! pub trait MyTrait {
24//! const SOME_STR: &'static str;
25//! }
26//! impl<A, B> MyTrait for (A, B)
27//! where
28//! A: MyTrait,
29//! B: MyTrait,
30//! {
31//! // evaluate our upper bound acceptor to implement concatenation
32//! const SOME_STR: &'static str = {
33//! let slice: &'static [u8] = gub::eval_with_upper_bound::<Concat<A, B>>();
34//!
35//! // take subslice without trailing zeros and convert to string
36//! let total_length = gub::desired_generic::<Concat<A, B>>();
37//! match core::str::from_utf8(slice.split_at(total_length).0) {
38//! Ok(s) => s,
39//! _ => unreachable!(),
40//! }
41//! };
42//! }
43//!
44//! struct Concat<A, B>(A, B);
45//! impl<A: MyTrait, B: MyTrait> gub::AcceptUpperBound for Concat<A, B> {
46//! type Output = &'static [u8];
47//! // Want to be passed at least the total length of the strings
48//! const DESIRED_GENERIC: usize = A::SOME_STR.len() + B::SOME_STR.len();
49//! // Decide on what to do with each generic const
50//! type Eval<const UPPER: usize> = ConcatImpl<A, B, UPPER>;
51//! }
52//!
53//! struct ConcatImpl<A, B, const N: usize>(A, B);
54//! impl<A, B, const N: usize> gub::Const for ConcatImpl<A, B, N>
55//! where
56//! A: MyTrait,
57//! B: MyTrait,
58//! {
59//! type Type = &'static [u8];
60//! // Write the bytes into `[u8; N]` and promote the result
61//! const VALUE: Self::Type = &{
62//! let l = A::SOME_STR.as_bytes();
63//! let r = B::SOME_STR.as_bytes();
64//! let mut out = [0; N];
65//! let mut off = 0;
66//! let mut i = 0; // in >=1.86, you can use split_at_mut and copy_from_slice
67//! while i < l.len() {
68//! out[off] = l[i];
69//! off += 1;
70//! i += 1;
71//! }
72//! i = 0;
73//! while i < r.len() {
74//! out[off] = r[i];
75//! off += 1;
76//! i += 1;
77//! }
78//! out
79//! };
80//! }
81//!
82//! impl MyTrait for () {
83//! const SOME_STR: &'static str = "ABC";
84//! }
85//! impl MyTrait for i32 {
86//! const SOME_STR: &'static str = "123";
87//! }
88//! let concatenated: &'static str = <((), i32)>::SOME_STR;
89//! assert_eq!(concatenated, "ABC123");
90//! ```
91//! Note that this example can be generalized and optimized. For instance, it is possible to accept
92//! any `&'a [&'b str]` where `'b: 'a` as input and this will also be more efficient (most of the
93//! time) due to the overhead from the inexact upper bound used for each concatenation (which will
94//! likely affect the final binary size).
95
96/// A trait for a type that holds a value.
97pub trait Const {
98 /// The type of the const
99 type Type;
100 /// The implementation of the value of the const. Use [`const_value`] for accessing the value.
101 const VALUE: Self::Type;
102}
103
104/// Alias for [`Const::Type`].
105pub type TypeOf<C> = <C as Const>::Type;
106
107/// Alias for [`Const::VALUE`]. Prefer this over accessing `VALUE` directly.
108///
109/// Using the associated constant through this function rather than directly causes it to only be
110/// evaluated when the branch that it is used in is actually executed, assuming that the execution
111/// happens at compile time (i.e. this does not apply to usage in regular `fn`s).
112/// This means that it may improve compile times, avoid errors for recursive consts and avoid evaluating
113/// panics.
114///
115/// For example:
116/// ```
117/// # use generic_upper_bound::*;
118/// struct Fallible;
119/// impl Const for Fallible {
120/// type Type = ();
121/// const VALUE: Self::Type = panic!();
122/// }
123/// const _: () = if false { const_value::<Fallible>() }; // this compiles
124/// ```
125/// ```compile_fail
126/// # use generic_upper_bound::*;
127/// # struct Fallible;
128/// # impl Const for Fallible {
129/// # type Type = ();
130/// # const VALUE: Self::Type = panic!();
131/// # }
132/// const _: () = if false { Fallible::VALUE }; // this gives a compile error
133/// ```
134pub const fn const_value<C: Const + ?Sized>() -> C::Type {
135 C::VALUE
136}
137
138/// Allows performing an evaluation of a [`Const`] after converting an associated const `usize` to a
139/// best-effort upper bound `const ...: usize`.
140pub trait AcceptUpperBound {
141 /// The output type of the evaluation.
142 type Output;
143
144 /// The desired value and lower bound that the implementor wants to be passed to [`Self::Eval`].
145 const DESIRED_GENERIC: usize;
146
147 /// Evals the constant by mapping a generic parameter that is at least the desired value
148 /// to the output value. `const_value::<Eval<N>>()` should be indistinguishable for
149 /// all `UPPER_BOUND >= DESIRED` passed to this.
150 type Eval<const UPPER: usize>: Const<Type = Self::Output>;
151}
152
153struct Impl<F>(F);
154
155mod implementation;
156
157/// Returns [`F::DESIRED_GENERIC`](AcceptUpperBound::DESIRED_GENERIC).
158pub const fn desired_generic<F: AcceptUpperBound>() -> usize {
159 Impl::<F>::DESIRED
160}
161
162/// Returns the parameter that [`eval_with_upper_bound`] passes to [`F::Eval`](AcceptUpperBound::Eval).
163pub const fn get_upper_bound<F: AcceptUpperBound>() -> usize {
164 Impl::<F>::ACTUAL
165}
166
167/// Evaluates [`AcceptUpperBound`].
168///
169/// In the language of `generic_const_exprs`, this function returns
170/// `const_value::<F::Eval<{ get_upper_bound::<F>() }>>()`
171pub const fn eval_with_upper_bound<F: AcceptUpperBound>() -> F::Output {
172 Impl::<F>::EVAL
173}