Re: [PATCH 2/2] rust: num: add `cv!` macro to create values from constant expressions

From: Eliot Courtney

Date: Mon Aug 31 2026 - 03:20:39 EST


On Fri Aug 28, 2026 at 9:03 PM JST, Gary Guo wrote:
> Currently, constructing a `NonZero` or `Bounded` from a constant is
> verbose. The former would require `const { NonZero::new(...).unwrap() }`
> and the latter require turbofish. Similarly, the `num::casts` exposes
> methods that cast numbers using turbofish syntax, which is unergonomic and
> unnecessarily causes the value to flow into the type system, which is very
> restrictive without `generic_const_exprs`.
>
> Implement a macro `cv!` (short for constant value) which converts a const
> integer to types that implements `FromConst` trait and validate them during
> const evaluation.
>
> The usage is of form
>
> cv!(<expression>)
>
> for inferred type and
>
> cv!(<expression> => <type>)
>
> for explicit type specification.
>
> As we do not have const trait implementation yet, dark magic is used. The
> dark magic is documented in the code, but in essence it defines inherent
> `__from_const` impls on types, which can be marked const, and rely on
> Rust's method resolution algorithm to pick the correct function. Multiple
> helpers are defined to aid type inference to work properly.
>
> As a result, this allows construction of primitive integers, `NonZero`,
> `Bounded`, `Alignment` using a single `cv!` macro. This macro does not have
> `generic_const_exprs` restrictions (e.g. in a function with `const N: u32`
> generic parameter, you may use `cv!(N + 1)`), it supports full type
> inference and it has nice error messages in some common error scenario:
>
> error[E0080]: evaluation panicked: constant is zero
> --> example.rs:22:25
> |
> 22 | const X: NonZero<u32> = cv!(0);
> | ^^^^^^ evaluation of `X::{constant#0}` failed inside this call
>
> error[E0277]: `kernel::page::Page` cannot be converted from constant
> --> example.rs:22:17
> |
> 22 | const X: Page = cv!(0);
> | ^^^^^^ the trait `kernel::num::FromConst` is not implemented for `kernel::page::Page`
>
> Of course, this trick is not full const trait impl. So the following code cannot work properly:
>
> fn generic<T: FromConst>() -> T {
> cv!(0)
> }
>
> That said, useful error message is still produced in this context.
>
> error[E0080]: evaluation panicked: `cv!()` cannot be used with generic types yet
> --> example.rs:23:5
> |
> 22 | cv!(0)
> | ^^^^^^ evaluation of `generic::<u32>::{constant#0}` failed inside this call
>
> Co-developed-by: Eliot Courtney <ecourtney@xxxxxxxxxx>
> Signed-off-by: Eliot Courtney <ecourtney@xxxxxxxxxx>
> Signed-off-by: Gary Guo <gary@xxxxxxxxxxx>
> ---
> I used part of
> https://lore.kernel.org/rust-for-linux/20260827-chid-v8-3-bc74c77d0214@xxxxxxxxxx
> so I added Co-developed-by tags of Eliot. Eliot, please let me know if this
> is okay.
> ---

Yes, co-developed-by/signed-off-by lgtm, no worries.

I had also considered using auto deref method resolution to solve this,
but I think it's a little overcomplicated, so I just posted the
associated const version instead. AFAICT this gives us the ability to
use const generic expressions on non-primitive types and type aliases of
primitive types, over associated const cv!. Currently there are no
(prospective) users for that ability.

I think also that this version is not a strict superset of the
functionality of the associated const based cv!. For example, associated
const cv! has better errors in some cases and can do things like this,
in the T: Trait case you noted (plus some edge cases around i128/u128
handling):

```fn zero<T: FromConst<0>>() -> T { cv!(0) }```

For the above reasons, plus what Alex mentioned, I prefer the simpler
associated const version for now, particularly since we could
transparently switch them later.

FWIW the version I was considering works a bit differently and uses a
functional list of types like (T, (List)) to define a method resolution
chain. It avoids needing the SFINAE like type probe machinery (I checked
and this approach works on your version too, which simplifies it). It
looks like this (adds Bounded::try_new_const), although I think the type
list idea is worse than yours with the wrapping type:

```
macro_rules! chain_of {
() => { () };
($h:ty $(, $r:ty)*) => { ($h, chain_of!($($r),*)) };
}

pub type Chain = chain_of!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);

pub struct Probe<T, L>(PhantomData<(T, L)>);

impl<T, L> Clone for Probe<T, L> {
fn clone(&self) -> Self {
*self
}
}
impl<T, L> Copy for Probe<T, L> {}

impl<T, H, R> Deref for Probe<T, (H, R)> {
type Target = Probe<T, R>;
fn deref(&self) -> &Self::Target {
build_error!("for candidate lookup only");
}
}

impl<T> Deref for Probe<T, ()> {
type Target = T;
fn deref(&self) -> &T {
build_error!("for candidate lookup only");
}
}

#[diagnostic::on_unimplemented(message = "`{Self}` cannot be converted from a constant")]
pub trait FromConstProbe: Sized {}

pub const fn pin<T, L>(_: &Probe<T, L>, v: T) -> T {
v
}

pub const fn probe_for<T: FromConstProbe>() -> Probe<T, Chain> {
Probe(PhantomData)
}

impl<T> Probe<T, ()> {
pub const fn __fc(self: &Probe<T, Chain>, _v: i128) -> T {
panic!("`cvp!` cannot be used with generic types yet");
}
}

macro_rules! impl_prims {
() => {};
($ty:ty $(, $rest:ty)*) => {
impl FromConstProbe for $ty {}

impl Probe<$ty, chain_of!($ty $(, $rest)*)> {
pub const fn __fc(self: Probe<$ty, Chain>, v: i128) -> $ty {
assert!(
v >= <$ty>::MIN as i128 && v <= <$ty>::MAX as i128,
concat!("constant cannot be represented by `", stringify!($ty), "`"),
);

v as $ty
}
}

impl FromConstProbe for NonZero<$ty> {}

impl Probe<NonZero<$ty>, chain_of!($ty $(, $rest)*)> {
pub const fn __fc(self: Probe<NonZero<$ty>, Chain>, v: i128) -> NonZero<$ty> {
assert!(
v >= <$ty>::MIN as i128 && v <= <$ty>::MAX as i128,
concat!("constant cannot be represented by `", stringify!($ty), "`"),
);

match NonZero::new(v as $ty) {
Some(x) => x,
None => panic!("constant is zero"),
}
}
}

impl<const N: u32> FromConstProbe for Bounded<$ty, N> {}

impl<const N: u32> Probe<Bounded<$ty, N>, chain_of!($ty $(, $rest)*)> {
pub const fn __fc(self: Probe<Bounded<$ty, N>, Chain>, v: i128) -> Bounded<$ty, N> {
assert!(
v >= <$ty>::MIN as i128 && v <= <$ty>::MAX as i128,
concat!("constant cannot be represented by `", stringify!($ty), "`"),
);

match Bounded::<$ty, N>::try_new_const(v as $ty) {
Some(b) => b,
None => panic!("constant cannot be represented within the given bits"),
}
}
}

impl_prims!($($rest),*);
};
}
impl_prims!(u8, u16, u32, u64, usize, i8, i16, i32, i64, isize);

impl FromConstProbe for Alignment {}

impl Alignment {
pub const fn __fc(self: Probe<Alignment, Chain>, v: i128) -> Alignment {
assert!(
v > 0 && v <= usize::MAX as i128,
"constant cannot be represented as an `Alignment`",
);

match Alignment::new_checked(v as usize) {
Some(a) => a,
None => panic!("constant is not a power of two"),
}
}
}

#[macro_export]
#[doc(hidden)]
macro_rules! cvp {
(@widen $v:expr) => {{
#[allow(
unused_comparisons,
unused_assignments,
clippy::as_underscore,
clippy::unnecessary_cast
)]
{
let v = $v;
let r = v as i128;
let mut back = v;
back = r as _;

::core::assert!(
back == v && (v < 0) == (r < 0),
"value cannot be losslessly widened to `i128`"
);

r
}
}};
($v:expr => $ty:ty) => {
const { $crate::num::cvp::probe_for::<$ty>().__fc($crate::cvp!(@widen $v)) }
};
($v:expr) => {
const {
let probe = $crate::num::cvp::probe_for();
$crate::num::cvp::pin(&probe, probe.__fc($crate::cvp!(@widen $v)))
}
};
}
```