From: Gary Guo <gary@garyguo.net>
To: Miguel Ojeda <ojeda@kernel.org>, Boqun Feng <boqun@kernel.org>,
Gary Guo <gary@garyguo.net>,
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>,
Abdiel Janulgue <abdiel.janulgue@gmail.com>,
Daniel Almeida <daniel.almeida@collabora.com>,
Robin Murphy <robin.murphy@arm.com>,
Alexandre Courbot <acourbot@nvidia.com>,
David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>
Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
driver-core@lists.linux.dev, nova-gpu@lists.linux.dev,
dri-devel@lists.freedesktop.org
Subject: [PATCH v2 3/6] rust: ptr: add panicking index projection variant
Date: Tue, 02 Jun 2026 15:17:54 +0100 [thread overview]
Message-ID: <20260602-projection-syntax-rework-v2-3-6989470f5440@garyguo.net> (raw)
In-Reply-To: <20260602-projection-syntax-rework-v2-0-6989470f5440@garyguo.net>
There have been a few cases where the programmer knows that the indices are
in bounds but the compiler cannot deduce that. This is also
compiler-version-dependent, so using build indexing here can be
problematic. On the other hand, it is also not ideal to use the fallible
variant, as it adds an error handling path that is never hit.
Add a new panicking index projection for this scenario. Like all panicking
operations, this should be used carefully only in cases where the user
knows the index is going to be in bounds, and panicking would indicate
something is catastrophically wrong.
To signify this, require users to explicitly denote the type of index being
used. The existing two types of index projections also gain the keyworded
version, which will be the recommended way going forward.
The keyworded syntax also paves the way of perhaps adding more flavors in
the future, e.g. `unsafe` index projection. However, unless the code is
extremely performance sensitive and bounds checking cannot be tolerated,
the panicking variant is safer and should be preferred, so it will be left
to the future when demand arises.
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/dma.rs | 3 ++
rust/kernel/ptr/projection.rs | 96 ++++++++++++++++++++++++++++++++++++-------
2 files changed, 84 insertions(+), 15 deletions(-)
diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs
index 8f97916e0688..ff69739eae35 100644
--- a/rust/kernel/dma.rs
+++ b/rust/kernel/dma.rs
@@ -1207,6 +1207,9 @@ macro_rules! dma_write {
(@parse [$dma:expr] [$($proj:tt)*] [.$field:tt $($rest:tt)*]) => {
$crate::dma_write!(@parse [$dma] [$($proj)* .$field] [$($rest)*])
};
+ (@parse [$dma:expr] [$($proj:tt)*] [[$flavor:ident: $index:expr] $($rest:tt)*]) => {
+ $crate::dma_write!(@parse [$dma] [$($proj)* [$flavor: $index]] [$($rest)*])
+ };
(@parse [$dma:expr] [$($proj:tt)*] [[$index:expr]? $($rest:tt)*]) => {
$crate::dma_write!(@parse [$dma] [$($proj)* [$index]?] [$($rest)*])
};
diff --git a/rust/kernel/ptr/projection.rs b/rust/kernel/ptr/projection.rs
index 1b54616c6cbb..e46877a208d9 100644
--- a/rust/kernel/ptr/projection.rs
+++ b/rust/kernel/ptr/projection.rs
@@ -26,14 +26,14 @@ fn from(_: OutOfBound) -> Self {
///
/// # Safety
///
-/// For given input pointer `slice` and return value `output`, the implementation of `build_index`
-/// and `get` (if [`Some`] is returned) must ensure that:
+/// For given input pointer `slice` and return value `output`, the implementation of `index`,
+/// `build_index` and `get` (if [`Some`] is returned) must ensure that:
/// - `output` has the same provenance as `slice`;
/// - `output.byte_offset_from(slice)` is between 0 to
/// `KnownSize::size(slice) - KnownSize::size(output)`.
///
-/// This means that if the input pointer is valid, then pointer returned by `get` or `build_index`
-/// is also valid.
+/// This means that if the input pointer is valid, then pointer returned by `get`, `index` or
+/// `build_index` is also valid.
#[diagnostic::on_unimplemented(message = "`{Self}` cannot be used to index `{T}`")]
#[doc(hidden)]
pub unsafe trait ProjectIndex<T: ?Sized>: Sized {
@@ -42,6 +42,9 @@ pub unsafe trait ProjectIndex<T: ?Sized>: Sized {
/// Returns an index-projected pointer, if in bounds.
fn get(self, slice: *mut T) -> Option<*mut Self::Output>;
+ /// Returns an index-projected pointer; panic if out of bounds.
+ fn index(self, slice: *mut T) -> *mut Self::Output;
+
/// Returns an index-projected pointer; fail the build if it cannot be proved to be in bounds.
#[inline(always)]
fn build_index(self, slice: *mut T) -> *mut Self::Output {
@@ -66,6 +69,11 @@ fn build_index(self, slice: *mut T) -> *mut Self::Output {
<I as ProjectIndex<[T]>>::get(self, slice)
}
+ #[inline(always)]
+ fn index(self, slice: *mut [T; N]) -> *mut Self::Output {
+ <I as ProjectIndex<[T]>>::index(self, slice)
+ }
+
#[inline(always)]
fn build_index(self, slice: *mut [T; N]) -> *mut Self::Output {
<I as ProjectIndex<[T]>>::build_index(self, slice)
@@ -85,6 +93,16 @@ fn get(self, slice: *mut [T]) -> Option<*mut T> {
Some(slice.cast::<T>().wrapping_add(self))
}
}
+
+ #[inline(always)]
+ fn index(self, slice: *mut [T]) -> *mut T {
+ // Leverage Rust built-in operators for bounds checking.
+ // SAFETY: All non-null and aligned pointers are valid for ZST read.
+ let zst_slice =
+ unsafe { core::slice::from_raw_parts::<()>(core::ptr::dangling(), slice.len()) };
+ let () = zst_slice[self];
+ slice.cast::<T>().wrapping_add(self)
+ }
}
// SAFETY: `get`-returned pointer has the same provenance as `slice` and the offset is checked to
@@ -103,6 +121,18 @@ fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
new_len,
))
}
+
+ #[inline(always)]
+ fn index(self, slice: *mut [T]) -> *mut [T] {
+ // Leverage Rust built-in operators for bounds checking.
+ // SAFETY: All non-null and aligned pointers are valid for ZST read.
+ let zst_slice =
+ unsafe { core::slice::from_raw_parts::<()>(core::ptr::dangling(), slice.len()) };
+ _ = zst_slice[self.clone()];
+
+ // SAFETY: bounds checked.
+ unsafe { self.get(slice).unwrap_unchecked() }
+ }
}
// SAFETY: Safety requirement guaranteed by the forwarded impl.
@@ -113,6 +143,11 @@ unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeTo<usize> {
fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
(0..self.end).get(slice)
}
+
+ #[inline(always)]
+ fn index(self, slice: *mut [T]) -> *mut [T] {
+ (0..self.end).index(slice)
+ }
}
// SAFETY: Safety requirement guaranteed by the forwarded impl.
@@ -123,6 +158,11 @@ unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeFrom<usize> {
fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
(self.start..slice.len()).get(slice)
}
+
+ #[inline(always)]
+ fn index(self, slice: *mut [T]) -> *mut [T] {
+ (self.start..slice.len()).index(slice)
+ }
}
// SAFETY: `get` returned the pointer as is, so it always has the same provenance and offset of 0.
@@ -133,6 +173,11 @@ unsafe impl<T> ProjectIndex<[T]> for core::ops::RangeFull {
fn get(self, slice: *mut [T]) -> Option<*mut [T]> {
Some(slice)
}
+
+ #[inline(always)]
+ fn index(self, slice: *mut [T]) -> *mut [T] {
+ slice
+ }
}
/// A helper trait to perform field projection.
@@ -211,9 +256,12 @@ unsafe fn proj<F>(_: *mut Self, _: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
/// `kernel::ptr::project!(mut ptr, projection)`. By default, a const pointer is created.
///
/// `ptr::project!` macro can perform both fallible indexing and build-time checked indexing.
-/// `[index]` form performs build-time bounds checking; if compiler fails to prove `[index]` is in
-/// bounds, compilation will fail. `[index]?` can be used to perform runtime bounds checking;
-/// `OutOfBound` error is raised via `?` if the index is out of bounds.
+/// The syntax is of form `[<flavor>: index]` where `flavor` indicates the way of handling index
+/// out-of-bound errors.
+/// - `try` will raise an `OutOfBound` error (which is convertible to `ERANGE`).
+/// - `build` will use [`build_assert!`](kernel::build_assert::build_assert) mechanism to have
+/// compiler validate the index is in bounds.
+/// - `panic` will cause a Rust [`panic!`] if index goes out of bound.
///
/// # Examples
///
@@ -231,17 +279,21 @@ unsafe fn proj<F>(_: *mut Self, _: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
/// }
/// ```
///
-/// Index projections are performed with `[index]`:
+/// Index projections are performed with `[<flavor>: index]`, where `flavor` is `try`, `build` or
+/// `panic`:
///
/// ```
/// fn proj(ptr: *const [u8; 32]) -> Result {
-/// let field_ptr: *const u8 = kernel::ptr::project!(ptr, [1]);
+/// let field_ptr: *const u8 = kernel::ptr::project!(ptr, [build: 1]);
/// // The following invocation, if uncommented, would fail the build.
/// //
-/// // kernel::ptr::project!(ptr, [128]);
+/// // kernel::ptr::project!(ptr, [build: 128]);
///
/// // This will raise an `OutOfBound` error (which is convertible to `ERANGE`).
-/// kernel::ptr::project!(ptr, [128]?);
+/// kernel::ptr::project!(ptr, [try: 128]);
+///
+/// // This will panic in runtime if executed.
+/// kernel::ptr::project!(ptr, [panic: 128]);
/// Ok(())
/// }
/// ```
@@ -251,7 +303,7 @@ unsafe fn proj<F>(_: *mut Self, _: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
/// ```
/// let ptr: *const [u8; 32] = core::ptr::dangling();
/// let field_ptr: Result<*const u8> = (|| -> Result<_> {
-/// Ok(kernel::ptr::project!(ptr, [128]?))
+/// Ok(kernel::ptr::project!(ptr, [try: 128]))
/// })();
/// assert!(field_ptr.is_err());
/// ```
@@ -260,7 +312,7 @@ unsafe fn proj<F>(_: *mut Self, _: impl FnOnce(*mut Self) -> *mut F) -> *mut F {
///
/// ```
/// let ptr: *mut [(u8, u16); 32] = core::ptr::dangling_mut();
-/// let field_ptr: *mut u16 = kernel::ptr::project!(mut ptr, [1].1);
+/// let field_ptr: *mut u16 = kernel::ptr::project!(mut ptr, [build: 1].1);
/// ```
#[macro_export]
macro_rules! project_pointer {
@@ -283,16 +335,30 @@ macro_rules! project_pointer {
$crate::ptr::project!(@gen $ptr, $($rest)*)
};
// Fallible index projection.
- (@gen $ptr:ident, [$index:expr]? $($rest:tt)*) => {
+ (@gen $ptr:ident, [try: $index:expr] $($rest:tt)*) => {
let $ptr = $crate::ptr::projection::ProjectIndex::get($index, $ptr)
.ok_or($crate::ptr::projection::OutOfBound)?;
$crate::ptr::project!(@gen $ptr, $($rest)*)
};
+ // Panicking index projection.
+ (@gen $ptr:ident, [panic: $index:expr] $($rest:tt)*) => {
+ let $ptr = $crate::ptr::projection::ProjectIndex::index($index, $ptr);
+ $crate::ptr::project!(@gen $ptr, $($rest)*)
+ };
// Build-time checked index projection.
- (@gen $ptr:ident, [$index:expr] $($rest:tt)*) => {
+ (@gen $ptr:ident, [build: $index:expr] $($rest:tt)*) => {
let $ptr = $crate::ptr::projection::ProjectIndex::build_index($index, $ptr);
$crate::ptr::project!(@gen $ptr, $($rest)*)
};
+
+ // For compatibility
+ (@gen $ptr:ident, [$index:expr]? $($rest:tt)*) => {
+ $crate::ptr::project!(@gen $ptr, [try: $index] $($rest)*)
+ };
+ (@gen $ptr:ident, [$index:expr] $($rest:tt)*) => {
+ $crate::ptr::project!(@gen $ptr, [build: $index] $($rest)*)
+ };
+
(mut $ptr:expr, $($proj:tt)*) => {{
let ptr: *mut _ = $ptr;
$crate::ptr::project!(@gen ptr, $($proj)*);
--
2.54.0
next prev parent reply other threads:[~2026-06-02 14:18 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-02 14:17 [PATCH v2 0/6] Rework index projection syntax Gary Guo
2026-06-02 14:17 ` [PATCH v2 1/6] rust: ptr: rename `ProjectIndex::index` to `build_index` Gary Guo
2026-06-04 2:43 ` Claude review: " Claude Code Review Bot
2026-06-02 14:17 ` [PATCH v2 2/6] rust: ptr: use `match` instead of `unwrap_or_else` for `build_index` Gary Guo
2026-06-04 2:43 ` Claude review: " Claude Code Review Bot
2026-06-02 14:17 ` Gary Guo [this message]
2026-06-04 2:43 ` Claude review: rust: ptr: add panicking index projection variant Claude Code Review Bot
2026-06-02 14:17 ` [PATCH v2 4/6] rust: dma: update to keyworded index projection syntax Gary Guo
2026-06-04 2:43 ` Claude review: " Claude Code Review Bot
2026-06-02 14:17 ` [PATCH v2 5/6] gpu: nova-core: convert to keyworded " Gary Guo
2026-06-04 2:43 ` Claude review: " Claude Code Review Bot
2026-06-02 14:17 ` [PATCH v2 6/6] rust: ptr: remove implicit index " Gary Guo
2026-06-04 2:43 ` Claude review: " Claude Code Review Bot
2026-06-02 15:03 ` [PATCH v2 0/6] Rework " Danilo Krummrich
2026-06-04 2:43 ` Claude review: " Claude Code Review Bot
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=20260602-projection-syntax-rework-v2-3-6989470f5440@garyguo.net \
--to=gary@garyguo.net \
--cc=a.hindborg@kernel.org \
--cc=abdiel.janulgue@gmail.com \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=driver-core@lists.linux.dev \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=nova-gpu@lists.linux.dev \
--cc=ojeda@kernel.org \
--cc=robin.murphy@arm.com \
--cc=rust-for-linux@vger.kernel.org \
--cc=simona@ffwll.ch \
--cc=tmgross@umich.edu \
/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