[PATCH 2/2] rust: num: add `cv!` macro to create values from constant expressions
From: Gary Guo
Date: Fri Aug 28 2026 - 08:05:47 EST
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.
---
rust/kernel/num.rs | 209 ++++++++++++++++++++++++++++++++++++++++++++-
rust/kernel/num/bounded.rs | 21 +++++
rust/kernel/ptr.rs | 21 ++++-
3 files changed, 249 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/num.rs b/rust/kernel/num.rs
index dbe848e30efe..a38a86c3fc9f 100644
--- a/rust/kernel/num.rs
+++ b/rust/kernel/num.rs
@@ -2,7 +2,16 @@
//! Additional numerical features for the kernel.
-use core::ops;
+use core::{
+ marker::PhantomData,
+ num::NonZero,
+ ops, //
+};
+
+use crate::{
+ build_assert::const_eval,
+ prelude::*, //
+};
pub mod bounded;
pub mod casts;
@@ -79,3 +88,201 @@ impl Integer for $type {
i128: Signed,
isize: Signed
);
+
+/// Types that can be created from an integer constant expression validated during const evaluation.
+#[diagnostic::on_unimplemented(message = "`{Self}` cannot be converted from constant")]
+pub trait FromConst: Sized {
+ /// Create `Self` from constant `v`, fails the build if `v` is not valid for `Self`.
+ ///
+ /// This function must only be called during const evaluation.
+ ///
+ /// # Note
+ ///
+ /// This function is for documentation purpose only, showing what the trait would look like when
+ /// const trait implementation is available. Use [`cv!`] macro instead of calling this function.
+ #[cfg(doc)]
+ fn from_const(v: i128) -> Self {
+ build_error!("For documentation purpose only");
+ }
+}
+
+// Helper for type inference in the `cv!` macro.
+#[doc(hidden)]
+pub struct FromConstInferHelper<T>(PhantomData<T>);
+
+impl<T> FromConstInferHelper<T> {
+ #[expect(clippy::new_without_default)]
+ #[const_eval]
+ pub const fn new() -> Self
+ where
+ // This is on function and not on the impl block so the function always exist. Otherwise we
+ // can "function exists but trait was not satisfied" error instead of "trait not
+ // implemented" error.
+ T: FromConst,
+ {
+ Self(PhantomData)
+ }
+
+ // A helper to help type inference to let type inference know that the return type of
+ // `__from_const` is exactly `T`.
+ #[const_eval]
+ pub const fn infer(self) -> T {
+ panic!("For type inference only");
+ }
+
+ // Convert to the `FromConstMethod`, so `__from_const` can be called.
+ //
+ // We have to split helpers because once a type implements `Deref`, the type must be known when
+ // calling method on it because Rust needs to walk the deref chain. Thus, `infer` is on the
+ // `FromConstInferHelper` which does not implement `Deref`, while `__from_const` is on
+ // `FromConstMethod` which we can use deref to dispatch.
+ #[const_eval]
+ pub const fn method(self) -> FromConstMethod<T> {
+ FromConstMethod(PhantomData)
+ }
+}
+
+// Helper type that we define `__from_const` inherent method on, so they can be marked as const.
+#[doc(hidden)]
+pub struct FromConstMethod<T>(PhantomData<T>);
+
+impl<T> FromConstMethod<T> {
+ // A fallback method that is selected if no `__from_const` can be found on concrete types. This
+ // is needed to avoid "__from_const" doesn't exist error, and have a proper "trait not
+ // implemented" error instead.
+ //
+ // This is selected after concrete `__from_const` because it takes `&self` as receiver, and not
+ // `self`; auto-ref has lower priority in method resolution, so methods that take
+ // `FromConstMethod<T>` is selected first.
+ //
+ // This method may also be selected without accompanying "trait not implemented" error if the
+ // type implementing `FromConst` is generic (e.g. `cv!(foo => T)`) in a function with `T:
+ // FromConst` bound; so it also produce a proper error message for that scenario.
+ #[const_eval]
+ pub const fn __from_const(&self, _: i128) -> T {
+ panic!("`cv!()` cannot be used with generic types yet");
+ }
+}
+
+// Enable the use of `FromConstMethod<T>` as receiver type on `FromConstMethodWrap<T>`.
+impl<T> ops::Deref for FromConstMethod<T> {
+ type Target = FromConstMethodWrap<T>;
+
+ #[inline(always)]
+ fn deref(&self) -> &Self::Target {
+ build_error!("For receiver only");
+ }
+}
+
+// Helper type that we define inherent method on for foreign types.
+#[doc(hidden)]
+pub struct FromConstMethodWrap<T>(PhantomData<T>);
+
+// Enable the use of `FromConstMethod<T>` as receiver type on `T`.
+impl<T> ops::Deref for FromConstMethodWrap<T> {
+ type Target = T;
+
+ #[inline(always)]
+ fn deref(&self) -> &Self::Target {
+ build_error!("For receiver only");
+ }
+}
+
+macro_rules! impl_from_const_primitive {
+ ($($ty:ty)*) => {$(
+ impl FromConst for $ty {}
+
+ impl FromConstMethodWrap<$ty> {
+ #[const_eval]
+ pub const fn __from_const(self: FromConstMethod<$ty>, 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 FromConst for NonZero<$ty> {}
+
+ impl FromConstMethodWrap<NonZero<$ty>> {
+ #[const_eval]
+ pub const fn __from_const(
+ self: FromConstMethod<NonZero<$ty>>, 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(v) => v,
+ None => panic!("constant is zero"),
+ }
+ }
+ }
+ )*};
+}
+
+impl_from_const_primitive!(
+ u8 u16 u32 u64 usize
+ i8 i16 i32 i64 isize
+);
+
+/// Creates a value from an integer constant expression, with validity checked at build time.
+///
+/// This works for any type that implements [`FromConst`]. If a target type is not specified, it is
+/// inferred from the context.
+///
+/// # Examples
+///
+/// ```
+/// use core::num::NonZero;
+/// use kernel::num::Bounded;
+/// use kernel::num::cv;
+/// use kernel::ptr::Alignment;
+///
+/// let v: NonZero<usize> = cv!(8);
+/// assert_eq!(v.get(), 8);
+///
+/// // Any integer constant expression works, not only literals.
+/// let m: NonZero<usize> = cv!(usize::MAX);
+/// assert_eq!(m.get(), usize::MAX);
+///
+/// let b: Bounded<u32, 4> = cv!(15);
+/// assert_eq!(b.get(), 15);
+///
+/// let a: Alignment = cv!(4096);
+/// assert_eq!(a.as_usize(), 4096);
+///
+/// // Explicit type specification.
+/// assert_eq!(cv!(1 => NonZero<u8>).get(), 1);
+/// ```
+#[macro_export]
+#[doc(hidden)]
+macro_rules! cv {
+ ($v:expr $(=> $ty:ty)?) => { const {
+ #[allow(unused_comparisons, unused_assignments, clippy::as_underscore)]
+ {
+ let v = $v;
+ let r = v as i128;
+ // Pin `back` to `v`'s type so `as _` casts back to the source type.
+ let mut back = v;
+ back = r as _;
+ ::core::assert!(
+ back == v && (v < 0) == (r < 0),
+ "value cannot be losslessly widened to `i128`"
+ );
+
+ let helper = $crate::num::FromConstInferHelper$(::<$ty>)?::new();
+ if false {
+ helper.infer()
+ } else {
+ helper.method().__from_const(r)
+ }
+ }
+ }};
+}
+#[doc(inline)]
+pub use cv;
diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs
index d192610a687d..2da4c5f8e799 100644
--- a/rust/kernel/num/bounded.rs
+++ b/rust/kernel/num/bounded.rs
@@ -13,6 +13,7 @@
};
use kernel::{
+ build_assert::const_eval,
num::Integer,
prelude::*, //
};
@@ -261,7 +262,27 @@ pub const fn new<const VALUE: $type>() -> Self {
// `N` bits.
unsafe { Self::__new(VALUE) }
}
+
+ #[doc(hidden)]
+ #[const_eval]
+ pub const fn __from_const(self: super::FromConstMethod<Self>, v: i128) -> Self {
+ assert!(
+ v >= <$type>::MIN as i128 && v <= <$type>::MAX as i128,
+ concat!("constant cannot be represented by `", stringify!($type), "`"),
+ );
+
+ assert!(
+ fits_within!(v as $type, $type, N),
+ "constant cannot be represented within given bits",
+ );
+
+ // SAFETY: the asserts above confirmed that `V` can be represented within `N`
+ // bits.
+ unsafe { Self::__new(v as $type) }
+ }
}
+
+ impl<const N: u32> super::FromConst for Bounded<$type, N> {}
)*
};
}
diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs
index 82acb531b17b..fec7ae71fe15 100644
--- a/rust/kernel/ptr.rs
+++ b/rust/kernel/ptr.rs
@@ -11,7 +11,10 @@
};
use core::num::NonZero;
-use crate::const_assert;
+use crate::{
+ build_assert::const_eval, //
+ prelude::*,
+};
/// Type representing an alignment, which is always a power of two.
///
@@ -164,8 +167,24 @@ pub const fn mask(self) -> usize {
// non-zero.
!(self.as_usize() - 1)
}
+
+ #[doc(hidden)]
+ #[const_eval]
+ pub const fn __from_const(self: crate::num::FromConstMethod<Self>, v: i128) -> Self {
+ assert!(
+ v >= 0 && v <= usize::MAX as i128,
+ "constant cannot be represented by `usize`"
+ );
+
+ match Alignment::new_checked(v as usize) {
+ Some(v) => v,
+ None => panic!("constant is not power of 2"),
+ }
+ }
}
+impl crate::num::FromConst for Alignment {}
+
/// Trait for items that can be aligned against an [`Alignment`].
pub trait Alignable: Sized {
/// Aligns `self` down to `alignment`.
--
2.54.0