Rust for Linux List
 help / color / mirror / Atom feed
From: "Eliot Courtney" <ecourtney@nvidia.com>
To: "Gary Guo" <gary@garyguo.net>,
	"Alexandre Courbot" <acourbot@nvidia.com>,
	"Eliot Courtney" <ecourtney@nvidia.com>,
	"Yury Norov" <yury.norov@gmail.com>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun@kernel.org>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Daniel Almeida" <daniel.almeida@collabora.com>,
	"Tamir Duberstein" <tamird@kernel.org>,
	"Onur Özkan" <work@onurozkan.dev>
Cc: <linux-kernel@vger.kernel.org>, <rust-for-linux@vger.kernel.org>
Subject: Re: [PATCH 2/2] rust: num: add `cv!` macro to create values from constant expressions
Date: Mon, 31 Aug 2026 16:20:13 +0900	[thread overview]
Message-ID: <DL2Y3TBTXYA0.25GP7MGS9UN31@nvidia.com> (raw)
In-Reply-To: <20260828-cv-v1-2-694a695ff17f@garyguo.net>

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@nvidia.com>
> Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
> Signed-off-by: Gary Guo <gary@garyguo.net>
> ---
> I used part of
> https://lore.kernel.org/rust-for-linux/20260827-chid-v8-3-bc74c77d0214@nvidia.com
> 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)))
        }
    };
}
```

  parent reply	other threads:[~2026-08-31  7:20 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-28 12:03 [PATCH 0/2] rust: num: add cv! macro to create values from constant expressions (alt) Gary Guo
2026-08-28 12:03 ` [PATCH 1/2] rust: build_assert: add utility to require const eval Gary Guo
2026-08-28 12:03 ` [PATCH 2/2] rust: num: add `cv!` macro to create values from constant expressions Gary Guo
2026-08-28 14:18   ` Gary Guo
2026-08-29  4:05   ` Alexandre Courbot
2026-08-31  7:20   ` Eliot Courtney [this message]
2026-08-31 13:09     ` Gary Guo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=DL2Y3TBTXYA0.25GP7MGS9UN31@nvidia.com \
    --to=ecourtney@nvidia.com \
    --cc=a.hindborg@kernel.org \
    --cc=acourbot@nvidia.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=dakr@kernel.org \
    --cc=daniel.almeida@collabora.com \
    --cc=gary@garyguo.net \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tamird@kernel.org \
    --cc=tmgross@umich.edu \
    --cc=work@onurozkan.dev \
    --cc=yury.norov@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox