Re: [PATCH v8 03/12] rust: num: add cv! macro to create values from constant expressions
"Alexandre Courbot" <[email protected]>
| Newsgroups | dev.linux.lists.nova-gpu,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel,org.kernel.vger.rust-for-linux |
|---|---|
| Message-ID | <[email protected]> |
On Fri Aug 28, 2026 at 9:08 AM JST, Eliot Courtney wrote: > On Thu Aug 27, 2026 at 11:55 PM JST, Alice Ryhl wrote: >> On Thu, Aug 27, 2026 at 4:37 PM Gary Guo <[email protected]> wrote: >>> >>> On Thu Aug 27, 2026 at 3:29 PM BST, Alexandre Courbot wrote: >>> > On Thu Aug 27, 2026 at 10:48 PM JST, Eliot Courtney wrote: >>> >> On Thu Aug 27, 2026 at 8:12 PM JST, Alexandre Courbot wrote: >>> >>> On Thu Aug 27, 2026 at 7:42 PM JST, Alexandre Courbot wrote: >>> >>>> On Thu Aug 27, 2026 at 6:32 PM JST, Alice Ryhl wrote: >>> >>>>> On Thu, Aug 27, 2026 at 04:28:31PM +0900, Eliot Courtney wrote: >>> >>>>>> Currently, using NonZero/Bounded constants is quite verbose. It's >>> >>>>>> unfortunate because it disincentivizes using it in interface boundaries. >>> >>>>>> Introduce a macro to make it nicer to use. The macro `cv!` (for constant >>> >>>>>> value) takes a const integer expression and widens it to i128 (at build >>> >>>>>> time only) before passing it as a const generic value to a new trait >>> >>>>>> function `FromConst::from_const`. The trait is implemented by NonZero, >>> >>>>>> Bounded, and Alignment and lets values of each be constructed from >>> >>>>>> constants without a verbose turbofish syntax. For example, >>> >>>>>> `const { NonZero::new(1).unwrap() }` can be written as `cv!(1)`. >>> >>>>>> >>> >>>>>> Suggested-by: Gary Guo <[email protected]> >>> >>>>>> Signed-off-by: Eliot Courtney <[email protected]> >>> >>>>> >>> >>>>> This doesn't work in const context, so I don't think this is a great >>> >>>>> strategy. >>> >>>>> >>> >>>>> I would want to use it for cases like this: >>> >>>>> >>> >>>>> drivers/android/binder/netlink.rs >>> >>>>> const BINDER_CMD_REPORT: u8 = kernel::uapi::BINDER_CMD_REPORT as u8; >>> >>>>> const BINDER_A_REPORT_ERROR: c_int = kernel::uapi::BINDER_A_REPORT_ERROR as c_int; >>> >>>>> const BINDER_A_REPORT_CONTEXT: c_int = kernel::uapi::BINDER_A_REPORT_CONTEXT as c_int; >>> >>>>> const BINDER_A_REPORT_FROM_PID: c_int = kernel::uapi::BINDER_A_REPORT_FROM_PID as c_int; >>> >>>>> const BINDER_A_REPORT_FROM_TID: c_int = kernel::uapi::BINDER_A_REPORT_FROM_TID as c_int; >>> >>>>> const BINDER_A_REPORT_TO_PID: c_int = kernel::uapi::BINDER_A_REPORT_TO_PID as c_int; >>> >>>>> const BINDER_A_REPORT_TO_TID: c_int = kernel::uapi::BINDER_A_REPORT_TO_TID as c_int; >>> >>>>> const BINDER_A_REPORT_IS_REPLY: c_int = kernel::uapi::BINDER_A_REPORT_IS_REPLY as c_int; >>> >>>>> const BINDER_A_REPORT_FLAGS: c_int = kernel::uapi::BINDER_A_REPORT_FLAGS as c_int; >>> >>>>> const BINDER_A_REPORT_CODE: c_int = kernel::uapi::BINDER_A_REPORT_CODE as c_int; >>> >>>>> const BINDER_A_REPORT_DATA_SIZE: c_int = kernel::uapi::BINDER_A_REPORT_DATA_SIZE as c_int; >>> >>>> >>> >>>> `const_as!` [1] should do the trick for this, provided you don't need to >>> >>>> create a const `NonZero`. >>> >>>> >>> >>>> [1] https://lore.kernel.org/all/[email protected]/ >>> >>> >>> >>> ... but I agree it would be nice to be able to use this in const >>> >>> context. And there is an overlap with `const_as!` that becomes more >>> >>> obvious the more I look at it. >>> >>> >>> >>> In for a penny, in for a pound of macro code as they say. Since we >>> >>> agreed on using macros, how about unifying both under the same `cv!` >>> >>> macro, with as many branches as we have types we want to initialize from >>> >>> a constant value? For instance: >>> >>> >>> >>> // Does what `const_as!` currently does under the hood. >>> >>> const BINDER_CMD_REPORT: u8 = cv!(u8::from(kernel::uapi::BINDER_CMD_REPORT)); >>> >>> // Calls `NonZero::new().unwrap()` under the hood. >>> >>> const SOME_NONZERO: NonZero<u8> = cv!(NonZero::new(kernel::uapi::NONZERO_VALUE)); >>> >>> // Calls `Bounded::new::<{ ...}>()` under the hood. >>> >>> const SOME_BOUNDED: Bounded<u32, 2> = cv!(Bounded::new(kernel::uapi::SMALL_VALUE)); >>> >>> >>> >>> I.e. we would have one extra matching arm in `cv!` per type it handles >>> >>> instead of implementing a trait. The syntax of the macro would look more >>> >>> natural (bye bye `const_as`'s awkward `=>`), albeit it would have the >>> >>> limitations of such a semantic dispatch. >>> >>> >>> >>> Even the name `const_as!` wasn't really accurate to begin with: what it >>> >>> really emulates is a const `try_from`, and we even discussed >>> >>> implementing it in these terms in the future. >>> >>> >>> >>> I'm sure the idea needs more polishing but I think there's something to >>> >>> explore here. >>> >> >>> >> Yeah I agree that const_as! is similar and if we had const traits we >>> >> could fully merge them and have it always work in a const context for >>> >> both duties (which are really a const tryfrom as you said). >>> >> >>> >> I am not sure about the suggested syntax (e.g. >>> >> cv!(Bounded::new(kernel::uapi::SMALL_VALUE))), since it seems very >>> >> verbose. >>> > >>> > A bit, but what I like is that it looks very close to what you would >>> > naturally write if you had const traits (minus the unwraps), so you >>> > don't have to learn a new syntax. As long as it's not *more* verbose >>> > than natural Rust, I think it's fine. >>> > >>> > It also has the benefit of relying less on type inference, i.e. `cv!(5)` >>> > requires the caller to specify the type even with a `let` statement, >>> > whereas you could do `let v = cv!(NonZero::new(5));` and it would work >>> > as expected. >>> >>> Even with const try_from we'd still want `cv!()` to avoid having to write >>> >>> const { Type::try_from(...).unwrap() } >>> >>> I think having `=>` syntax is great because it is a good place to *optionally* >>> require type annotation. >>> >>> For enum repr for example, I think it'd be great that >>> >>> const BINDER_CMD_REPORT: u8 = cv!(kernel::uapi::BINDER_CMD_REPORT); >>> >>> would work directly. It might need some tricks, which I have hard time coming up >>> as I'm not feeling very well today, but I'll give it a shot over the weekend... >> >> One could potentially define a trait with a MIN and MAX value >> constant, and then implement cv! like this: >> >> 1. Verify that the value lies between MIN and MAX. >> 2. Cast the value to uNN of the same size as the target type. >> 3. Transmute the uNN to the target type. >> >> Since the trait has no methods, this works in const eval. >> >> Alice > > Using an associated const by itself appears to work - I tried this which > is very similar to Alice's suggested approach above: > > ``` > macro_rules! const_assert { > ($condition:expr $(,$arg:literal)?) => { > const { ::core::assert!($condition $(,$arg)?) }; > }; > } Any reason this cannot use the `const_assert` already in the kernel crate? > > trait FromConst<const V: i128>: Sized { > const VALUE: Self; > } > > macro_rules! cv { > (@widen $v:expr) => {{ > #[allow(unused_comparisons, unused_assignments)] > { > 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 => $t:ty) => { > <$t as FromConst<{ cv!(@widen $v) }>>::VALUE nit for the actual posting: make sure to fully qualify `cv` when calling it recursively (and make sure all symbols are fully qualified). > }; > ($v:expr) => { > <_ as FromConst<{ cv!(@widen $v) }>>::VALUE > }; > } > > macro_rules! impl_from_const_int { > ($($t:ty)*) => {$( > impl<const V: i128> FromConst<V> for $t { > const VALUE: Self = { > const_assert!( > V >= <$t>::MIN as i128 && V <= <$t>::MAX as i128, > "Constant cannot be represented by the target type." > ); > V as $t > }; > } > > impl<const V: i128> FromConst<V> for NonZero<$t> { > const VALUE: Self = { > const_assert!( > V >= <$t>::MIN as i128 && V <= <$t>::MAX as i128, > "Constant cannot be represented by the underlying type." > ); Let's also have a `const_assert!(V != 0, ...)` to provide a better error message than "unwrap on None" if users call this with 0. > NonZero::new(V as $t).unwrap() > }; > } > )*}; > } > impl_from_const_int!(u8 u16 u32 u64 usize i8 i16 i32 i64 isize); > > impl<const V: i128> FromConst<V> for Alignment { > const VALUE: Self = { > const_assert!(V > 0 && V <= usize::MAX as i128); > // The unwrap fails the build if `V` is not a power of two. > Alignment::new_checked(V as usize).unwrap() > }; > } > > const A: u8 = cv!(200u32); > const B: NonZero<u8> = cv!(5); > const C: Alignment = cv!(4096); > const D: u8 = cv!(200u32 => u8); > ``` That looks like it could work! IIUC it even supports something like `cv!(x => NonZero<u8>)`. The only limitation is see is that this cannot take expressions using generic parameters, but we can probably work around that. I guess you'll want to split this out into its own series so it doesn't remain hidden within the ranges/bitmap work. Basically as a replacement for the `const_as` I was driving [1]. I'll recycle the `const_as` series to just switch to the kernel converters, and will follow-up with using `cv!` once it lands. Since this is going to be a multi-cycle effort we should probably keep the legacy `*_into_*` functions around for now and remove them in another patch once all users are converted. [1] https://lore.kernel.org/[email protected]