Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 10 additions & 29 deletions library/core/src/ptr/const_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ impl<T: PointeeSized> *const T {

#[doc = include_str!("./docs/offset.md")]
///
/// Consider using [`wrapping_offset`](#method.wrapping_offset) instead if these constraints are
/// difficult to satisfy. The only advantage of this method is that it
/// enables more aggressive compiler optimizations.
///
/// # Examples
///
/// ```
Expand Down Expand Up @@ -810,6 +814,10 @@ impl<T: PointeeSized> *const T {

#[doc = include_str!("./docs/add.md")]
///
/// Consider using [`wrapping_add`](#method.wrapping_add) instead if these constraints are
/// difficult to satisfy. The only advantage of this method is that it
/// enables more aggressive compiler optimizations.
///
/// # Examples
///
/// ```
Expand Down Expand Up @@ -883,39 +891,12 @@ impl<T: PointeeSized> *const T {
unsafe { self.cast::<u8>().add(count).with_metadata_of(self) }
}

/// Subtracts an unsigned offset from a pointer.
///
/// This can only move the pointer backward (or not move it). If you need to move forward or
/// backward depending on the value, then you might want [`offset`](#method.offset) instead
/// which takes a signed offset.
///
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
/// offset of `3 * size_of::<T>()` bytes.
#[doc = include_str!("./docs/sub.md")]
///
/// # Safety
///
/// If any of the following conditions are violated, the result is Undefined Behavior:
///
/// * The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
/// "wrapping around"), must fit in an `isize`.
///
/// * If the computed offset is non-zero, then `self` must be [derived from][crate::ptr#provenance] a pointer to some
/// [allocation], and the entire memory range between `self` and the result must be in
/// bounds of that allocation. In particular, this range must not "wrap around" the edge
/// of the address space.
///
/// Allocations can never be larger than `isize::MAX` bytes, so if the computed offset
/// stays in bounds of the allocation, it is guaranteed to satisfy the first requirement.
/// This implies, for instance, that `vec.as_ptr().add(vec.len())` (for `vec: Vec<T>`) is always
/// safe.
///
/// Consider using [`wrapping_sub`] instead if these constraints are
/// Consider using [`wrapping_sub`](#method.wrapping_sub) instead if these constraints are
/// difficult to satisfy. The only advantage of this method is that it
/// enables more aggressive compiler optimizations.
///
/// [`wrapping_sub`]: #method.wrapping_sub
/// [allocation]: crate::ptr#allocation
///
/// # Examples
///
/// ```
Expand Down
20 changes: 8 additions & 12 deletions library/core/src/ptr/docs/add.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,15 @@ If any of the following conditions are violated, the result is Undefined Behavio
* The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
"wrapping around"), must fit in an `isize`.

* If the computed offset is non-zero, then `self` must be [derived from][crate::ptr#provenance] a pointer to some
[allocation], and the entire memory range between `self` and the result must be in
bounds of that allocation. In particular, this range must not "wrap around" the edge
of the address space.
* Let `result` be `self.addr() + count * size_of::<T>()`, computed on mathematical integers.
This must fit in a `usize`.

Allocations can never be larger than `isize::MAX` bytes, so if the computed offset
stays in bounds of the allocation, it is guaranteed to satisfy the first requirement.
This implies, for instance, that `vec.as_ptr().add(vec.len())` (for `vec: Vec<T>`) is always
safe.
* If the computed offset is non-zero, then `self` must be [derived from][crate::ptr#provenance] a pointer to some
[allocation], and the entire memory range between `self` and `result`
(i.e., `self.addr()..result`) must be in bounds of that allocation.

Consider using [`wrapping_add`] instead if these constraints are
difficult to satisfy. The only advantage of this method is that it
enables more aggressive compiler optimizations.
Allocations can never be larger than `isize::MAX` bytes and they can only contain addresses
representable by `usize`, so technically the last condition implies the first two. This implies, for
instance, that `vec.as_ptr().add(vec.len())` (for `vec: Vec<T>`) is always safe.

[`wrapping_add`]: #method.wrapping_add
[allocation]: crate::ptr#allocation
22 changes: 9 additions & 13 deletions library/core/src/ptr/docs/offset.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,16 @@ If any of the following conditions are violated, the result is Undefined Behavio
* The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
"wrapping around"), must fit in an `isize`.

* If the computed offset is non-zero, then `self` must be [derived from][crate::ptr#provenance] a pointer to some
[allocation], and the entire memory range between `self` and the result must be in
bounds of that allocation. In particular, this range must not "wrap around" the edge
of the address space. Note that "range" here refers to a half-open range as usual in Rust,
i.e., `self..result` for non-negative offsets and `result..self` for negative offsets.
* Let `result` be `self.addr() + count * size_of::<T>()`, computed on mathematical integers.
This must fit in a `usize`.

Allocations can never be larger than `isize::MAX` bytes, so if the computed offset
stays in bounds of the allocation, it is guaranteed to satisfy the first requirement.
This implies, for instance, that `vec.as_ptr().add(vec.len())` (for `vec: Vec<T>`) is always
safe.
* If the computed offset is non-zero, then `self` must be [derived from][crate::ptr#provenance] a pointer to some
[allocation], and the entire memory range between `self` and `result`
(i.e., `min(self.addr(), result)..max(self.addr(), result)`)
must be in bounds of that allocation.

Consider using [`wrapping_offset`] instead if these constraints are
difficult to satisfy. The only advantage of this method is that it
enables more aggressive compiler optimizations.
Allocations can never be larger than `isize::MAX` bytes and they can only contain addresses
representable by `usize`, so technically the last condition implies the first two. This implies, for
instance, that `vec.as_ptr().offset(vec.len() as isize)` (for `vec: Vec<T>`) is always safe.

[`wrapping_offset`]: #method.wrapping_offset
[allocation]: crate::ptr#allocation
27 changes: 27 additions & 0 deletions library/core/src/ptr/docs/sub.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Subtracts an unsigned offset from a pointer.

This can only move the pointer backward (or not move it). If you need to move forward or
backward depending on the value, then you might want [`offset`](#method.offset) instead
which takes a signed offset.

`count` is in units of T; e.g., a `count` of 3 represents a pointer
offset of `3 * size_of::<T>()` bytes.
Comment on lines +7 to +8

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm misunderstanding what you mean by this, doesn't this mean count * size_of::<T>() later on is effectively N * size_of::<T>() * size_of::<T>(), which is obviously incorrect? On its own, the safety section looks fine, but the addition of this callout seems unusual.

@RalfJung RalfJung Jun 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what you mean, and I didn't even change this text. Where are we multiplying twice? We are just defining "offset in bytes" twice, once by example in the introduction and once more generally in the safety docs.

This is the equivalent of

let offset_in_bytes = count * size;
let offset_in_bytes = count * size;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bit I highlighted sounds like you're defining count, not "offset in bytes".

@RalfJung RalfJung Jun 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read this 5 times now and I cannot figure out which part of it sounds to you like it defines count. It defines how to interpret count. count is a function argument given by the user, we cannot define it.

Since this is the same wording we already have on stable and had for many years, can we move this discussion into an issue or so? It's not really about the changes this PR proposes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, I didn't realize that was pre-existing. I don't care enough to open an issue for this; it's moreso that I found it confusing at first read.

@bors r+ rollup


# Safety

If any of the following conditions are violated, the result is Undefined Behavior:

* The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
"wrapping around"), must fit in an `isize`.

* Let `result` be `self.addr() - count * size_of::<T>()`, computed on mathematical integers.
This must fit in a `usize`.

* If the computed offset is non-zero, then `self` must be [derived from][crate::ptr#provenance] a pointer to some
[allocation], and the entire memory range between `self` and `result`
(i.e., `result..self.addr()`) must be in bounds of that allocation.

Allocations can never be larger than `isize::MAX` bytes and they can only contain addresses
representable by `usize`, so technically the last condition implies the first two.

[allocation]: crate::ptr#allocation
39 changes: 10 additions & 29 deletions library/core/src/ptr/mut_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,10 @@ impl<T: PointeeSized> *mut T {

#[doc = include_str!("./docs/offset.md")]
///
/// Consider using [`wrapping_offset`](#method.wrapping_offset) instead if these constraints are
/// difficult to satisfy. The only advantage of this method is that it
/// enables more aggressive compiler optimizations.
///
/// # Examples
///
/// ```
Expand Down Expand Up @@ -908,6 +912,10 @@ impl<T: PointeeSized> *mut T {

#[doc = include_str!("./docs/add.md")]
///
/// Consider using [`wrapping_add`](#method.wrapping_add) instead if these constraints are
/// difficult to satisfy. The only advantage of this method is that it
/// enables more aggressive compiler optimizations.
///
/// # Examples
///
/// ```
Expand Down Expand Up @@ -981,39 +989,12 @@ impl<T: PointeeSized> *mut T {
unsafe { self.cast::<u8>().add(count).with_metadata_of(self) }
}

/// Subtracts an unsigned offset from a pointer.
///
/// This can only move the pointer backward (or not move it). If you need to move forward or
/// backward depending on the value, then you might want [`offset`](#method.offset) instead
/// which takes a signed offset.
///
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
/// offset of `3 * size_of::<T>()` bytes.
#[doc = include_str!("./docs/sub.md")]
///
/// # Safety
///
/// If any of the following conditions are violated, the result is Undefined Behavior:
///
/// * The offset in bytes, `count * size_of::<T>()`, computed on mathematical integers (without
/// "wrapping around"), must fit in an `isize`.
///
/// * If the computed offset is non-zero, then `self` must be [derived from][crate::ptr#provenance] a pointer to some
/// [allocation], and the entire memory range between `self` and the result must be in
/// bounds of that allocation. In particular, this range must not "wrap around" the edge
/// of the address space.
///
/// Allocations can never be larger than `isize::MAX` bytes, so if the computed offset
/// stays in bounds of the allocation, it is guaranteed to satisfy the first requirement.
/// This implies, for instance, that `vec.as_ptr().add(vec.len())` (for `vec: Vec<T>`) is always
/// safe.
///
/// Consider using [`wrapping_sub`] instead if these constraints are
/// Consider using [`wrapping_sub`](#method.wrapping_sub) instead if these constraints are
/// difficult to satisfy. The only advantage of this method is that it
/// enables more aggressive compiler optimizations.
///
/// [`wrapping_sub`]: #method.wrapping_sub
/// [allocation]: crate::ptr#allocation
///
/// # Examples
///
/// ```
Expand Down
70 changes: 3 additions & 67 deletions library/core/src/ptr/non_null.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,28 +528,7 @@ impl<T: PointeeSized> NonNull<T> {
if self.is_aligned_to(align_of::<U>()) { Some(self.cast()) } else { None }
}

/// Adds an offset to a pointer.
///
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
/// offset of `3 * size_of::<T>()` bytes.
///
/// # Safety
///
/// If any of the following conditions are violated, the result is Undefined Behavior:
///
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
///
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
/// [allocation], and the entire memory range between `self` and the result must be in
/// bounds of that allocation. In particular, this range must not "wrap around" the edge
/// of the address space.
///
/// Allocations can never be larger than `isize::MAX` bytes, so if the computed offset
/// stays in bounds of the allocation, it is guaranteed to satisfy the first requirement.
/// This implies, for instance, that `vec.as_ptr().add(vec.len())` (for `vec: Vec<T>`) is always
/// safe.
///
/// [allocation]: crate::ptr#allocation
#[doc = include_str!("./docs/offset.md")]
///
/// # Examples
///
Expand Down Expand Up @@ -604,28 +583,7 @@ impl<T: PointeeSized> NonNull<T> {
unsafe { transmute(self.as_ptr().byte_offset(count)) }
}

/// Adds an offset to a pointer (convenience for `.offset(count as isize)`).
///
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
/// offset of `3 * size_of::<T>()` bytes.
///
/// # Safety
///
/// If any of the following conditions are violated, the result is Undefined Behavior:
///
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
///
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
/// [allocation], and the entire memory range between `self` and the result must be in
/// bounds of that allocation. In particular, this range must not "wrap around" the edge
/// of the address space.
///
/// Allocations can never be larger than `isize::MAX` bytes, so if the computed offset
/// stays in bounds of the allocation, it is guaranteed to satisfy the first requirement.
/// This implies, for instance, that `vec.as_ptr().add(vec.len())` (for `vec: Vec<T>`) is always
/// safe.
///
/// [allocation]: crate::ptr#allocation
#[doc = include_str!("./docs/add.md")]
///
/// # Examples
///
Expand Down Expand Up @@ -680,29 +638,7 @@ impl<T: PointeeSized> NonNull<T> {
unsafe { transmute(self.as_ptr().byte_add(count)) }
}

/// Subtracts an offset from a pointer (convenience for
/// `.offset((count as isize).wrapping_neg())`).
///
/// `count` is in units of T; e.g., a `count` of 3 represents a pointer
/// offset of `3 * size_of::<T>()` bytes.
///
/// # Safety
///
/// If any of the following conditions are violated, the result is Undefined Behavior:
///
/// * The computed offset, `count * size_of::<T>()` bytes, must not overflow `isize`.
///
/// * If the computed offset is non-zero, then `self` must be derived from a pointer to some
/// [allocation], and the entire memory range between `self` and the result must be in
/// bounds of that allocation. In particular, this range must not "wrap around" the edge
/// of the address space.
///
/// Allocations can never be larger than `isize::MAX` bytes, so if the computed offset
/// stays in bounds of the allocation, it is guaranteed to satisfy the first requirement.
/// This implies, for instance, that `vec.as_ptr().add(vec.len())` (for `vec: Vec<T>`) is always
/// safe.
///
/// [allocation]: crate::ptr#allocation
#[doc = include_str!("./docs/sub.md")]
///
/// # Examples
///
Expand Down
Loading