Rollup merge of #83579 - RalfJung:ptr-arithmetic, r=dtolnay

Improve pointer arithmetic docs

* Add slightly more detailed definition of "allocated object" to the module docs, and link it from everywhere.
* Clarify the "remains attached" wording a bit (at least I hope this is clearer).
* Remove the sentence about using integer arithmetic; this seems to confuse people even if it is technically correct.

As usual, the edit needs to be done in a dozen places to remain consistent, I hope I got them all.
This commit is contained in:
Dylan DPC 2021-03-30 11:34:26 +02:00 committed by GitHub
commit 5b67543c98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 47 additions and 55 deletions

View file

@ -184,8 +184,7 @@ impl<T: ?Sized> *const T {
/// Behavior:
///
/// * Both the starting and resulting pointer must be either in bounds or one
/// byte past the end of the same allocated object. Note that in Rust,
/// every (stack-allocated) variable is considered a separate allocated object.
/// byte past the end of the same [allocated object].
///
/// * The computed offset, **in bytes**, cannot overflow an `isize`.
///
@ -210,6 +209,7 @@ impl<T: ?Sized> *const T {
/// enables more aggressive compiler optimizations.
///
/// [`wrapping_offset`]: #method.wrapping_offset
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -245,9 +245,8 @@ impl<T: ?Sized> *const T {
///
/// This operation itself is always safe, but using the resulting pointer is not.
///
/// The resulting pointer remains attached to the same allocated object that `self` points to.
/// It may *not* be used to access a different allocated object. Note that in Rust, every
/// (stack-allocated) variable is considered a separate allocated object.
/// The resulting pointer "remembers" the [allocated object] that `self` points to; it may not
/// be used to read or write other allocated objects.
///
/// In other words, `let z = x.wrapping_offset((y as isize) - (x as isize))` does *not* make `z`
/// the same as `y` even if we assume `T` has size `1` and there is no overflow: `z` is still
@ -265,10 +264,8 @@ impl<T: ?Sized> *const T {
/// `x.wrapping_offset(o).wrapping_offset(o.wrapping_neg())` is always the same as `x`. In other
/// words, leaving the allocated object and then re-entering it later is permitted.
///
/// If you need to cross object boundaries, cast the pointer to an integer and
/// do the arithmetic there.
///
/// [`offset`]: #method.offset
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -314,8 +311,7 @@ impl<T: ?Sized> *const T {
/// Behavior:
///
/// * Both the starting and other pointer must be either in bounds or one
/// byte past the end of the same allocated object. Note that in Rust,
/// every (stack-allocated) variable is considered a separate allocated object.
/// byte past the end of the same [allocated object].
///
/// * Both pointers must be *derived from* a pointer to the same object.
/// (See below for an example.)
@ -345,6 +341,7 @@ impl<T: ?Sized> *const T {
/// such large allocations either.)
///
/// [`add`]: #method.add
/// [allocated object]: crate::ptr#allocated-object
///
/// # Panics
///
@ -468,8 +465,7 @@ impl<T: ?Sized> *const T {
/// Behavior:
///
/// * Both the starting and resulting pointer must be either in bounds or one
/// byte past the end of the same allocated object. Note that in Rust,
/// every (stack-allocated) variable is considered a separate allocated object.
/// byte past the end of the same [allocated object].
///
/// * The computed offset, **in bytes**, cannot overflow an `isize`.
///
@ -494,6 +490,7 @@ impl<T: ?Sized> *const T {
/// enables more aggressive compiler optimizations.
///
/// [`wrapping_add`]: #method.wrapping_add
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -532,8 +529,7 @@ impl<T: ?Sized> *const T {
/// Behavior:
///
/// * Both the starting and resulting pointer must be either in bounds or one
/// byte past the end of the same allocated object. Note that in Rust,
/// every (stack-allocated) variable is considered a separate allocated object.
/// byte past the end of the same [allocated object].
///
/// * The computed offset cannot exceed `isize::MAX` **bytes**.
///
@ -558,6 +554,7 @@ impl<T: ?Sized> *const T {
/// enables more aggressive compiler optimizations.
///
/// [`wrapping_sub`]: #method.wrapping_sub
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -594,9 +591,8 @@ impl<T: ?Sized> *const T {
///
/// This operation itself is always safe, but using the resulting pointer is not.
///
/// The resulting pointer remains attached to the same allocated object that `self` points to.
/// It may *not* be used to access a different allocated object. Note that in Rust, every
/// (stack-allocated) variable is considered a separate allocated object.
/// The resulting pointer "remembers" the [allocated object] that `self` points to; it may not
/// be used to read or write other allocated objects.
///
/// In other words, `let z = x.wrapping_add((y as usize) - (x as usize))` does *not* make `z`
/// the same as `y` even if we assume `T` has size `1` and there is no overflow: `z` is still
@ -614,10 +610,8 @@ impl<T: ?Sized> *const T {
/// `x.wrapping_add(o).wrapping_sub(o)` is always the same as `x`. In other words, leaving the
/// allocated object and then re-entering it later is permitted.
///
/// If you need to cross object boundaries, cast the pointer to an integer and
/// do the arithmetic there.
///
/// [`add`]: #method.add
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -659,9 +653,8 @@ impl<T: ?Sized> *const T {
///
/// This operation itself is always safe, but using the resulting pointer is not.
///
/// The resulting pointer remains attached to the same allocated object that `self` points to.
/// It may *not* be used to access a different allocated object. Note that in Rust, every
/// (stack-allocated) variable is considered a separate allocated object.
/// The resulting pointer "remembers" the [allocated object] that `self` points to; it may not
/// be used to read or write other allocated objects.
///
/// In other words, `let z = x.wrapping_sub((x as usize) - (y as usize))` does *not* make `z`
/// the same as `y` even if we assume `T` has size `1` and there is no overflow: `z` is still
@ -679,10 +672,8 @@ impl<T: ?Sized> *const T {
/// `x.wrapping_add(o).wrapping_sub(o)` is always the same as `x`. In other words, leaving the
/// allocated object and then re-entering it later is permitted.
///
/// If you need to cross object boundaries, cast the pointer to an integer and
/// do the arithmetic there.
///
/// [`sub`]: #method.sub
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -997,7 +988,7 @@ impl<T> *const [T] {
/// * The pointer must be [valid] for reads for `ptr.len() * mem::size_of::<T>()` many bytes,
/// and it must be properly aligned. This means in particular:
///
/// * The entire memory range of this slice must be contained within a single allocated object!
/// * The entire memory range of this slice must be contained within a single [allocated object]!
/// Slices can never span across multiple allocated objects.
///
/// * The pointer must be aligned even for zero-length slices. One
@ -1019,6 +1010,7 @@ impl<T> *const [T] {
/// See also [`slice::from_raw_parts`][].
///
/// [valid]: crate::ptr#safety
/// [allocated object]: crate::ptr#allocated-object
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
pub unsafe fn as_uninit_slice<'a>(self) -> Option<&'a [MaybeUninit<T>]> {

View file

@ -55,6 +55,14 @@
//! has size 0, i.e., even if memory is not actually touched. Consider using
//! [`NonNull::dangling`] in such cases.
//!
//! ## Allocated object
//!
//! For several operations, such as [`offset`] or field projections (`expr.field`), the notion of an
//! "allocated object" becomes relevant. An allocated object is a contiguous region of memory.
//! Common examples of allocated objects include stack-allocated variables (each variable is a
//! separate allocated object), heap allocations (each allocation created by the global allocator is
//! a separate allocated object), and `static` variables.
//!
//! [aliasing]: ../../nomicon/aliasing.html
//! [book]: ../../book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer
//! [ub]: ../../reference/behavior-considered-undefined.html

View file

@ -189,8 +189,7 @@ impl<T: ?Sized> *mut T {
/// Behavior:
///
/// * Both the starting and resulting pointer must be either in bounds or one
/// byte past the end of the same allocated object. Note that in Rust,
/// every (stack-allocated) variable is considered a separate allocated object.
/// byte past the end of the same [allocated object].
///
/// * The computed offset, **in bytes**, cannot overflow an `isize`.
///
@ -215,6 +214,7 @@ impl<T: ?Sized> *mut T {
/// enables more aggressive compiler optimizations.
///
/// [`wrapping_offset`]: #method.wrapping_offset
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -251,9 +251,8 @@ impl<T: ?Sized> *mut T {
///
/// This operation itself is always safe, but using the resulting pointer is not.
///
/// The resulting pointer remains attached to the same allocated object that `self` points to.
/// It may *not* be used to access a different allocated object. Note that in Rust, every
/// (stack-allocated) variable is considered a separate allocated object.
/// The resulting pointer "remembers" the [allocated object] that `self` points to; it may not
/// be used to read or write other allocated objects.
///
/// In other words, `let z = x.wrapping_offset((y as isize) - (x as isize))` does *not* make `z`
/// the same as `y` even if we assume `T` has size `1` and there is no overflow: `z` is still
@ -271,10 +270,8 @@ impl<T: ?Sized> *mut T {
/// `x.wrapping_offset(o).wrapping_offset(o.wrapping_neg())` is always the same as `x`. In other
/// words, leaving the allocated object and then re-entering it later is permitted.
///
/// If you need to cross object boundaries, cast the pointer to an integer and
/// do the arithmetic there.
///
/// [`offset`]: #method.offset
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -485,8 +482,7 @@ impl<T: ?Sized> *mut T {
/// Behavior:
///
/// * Both the starting and other pointer must be either in bounds or one
/// byte past the end of the same allocated object. Note that in Rust,
/// every (stack-allocated) variable is considered a separate allocated object.
/// byte past the end of the same [allocated object].
///
/// * Both pointers must be *derived from* a pointer to the same object.
/// (See below for an example.)
@ -516,6 +512,7 @@ impl<T: ?Sized> *mut T {
/// such large allocations either.)
///
/// [`add`]: #method.add
/// [allocated object]: crate::ptr#allocated-object
///
/// # Panics
///
@ -575,8 +572,7 @@ impl<T: ?Sized> *mut T {
/// Behavior:
///
/// * Both the starting and resulting pointer must be either in bounds or one
/// byte past the end of the same allocated object. Note that in Rust,
/// every (stack-allocated) variable is considered a separate allocated object.
/// byte past the end of the same [allocated object].
///
/// * The computed offset, **in bytes**, cannot overflow an `isize`.
///
@ -639,8 +635,7 @@ impl<T: ?Sized> *mut T {
/// Behavior:
///
/// * Both the starting and resulting pointer must be either in bounds or one
/// byte past the end of the same allocated object. Note that in Rust,
/// every (stack-allocated) variable is considered a separate allocated object.
/// byte past the end of the same [allocated object].
///
/// * The computed offset cannot exceed `isize::MAX` **bytes**.
///
@ -665,6 +660,7 @@ impl<T: ?Sized> *mut T {
/// enables more aggressive compiler optimizations.
///
/// [`wrapping_sub`]: #method.wrapping_sub
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -701,9 +697,8 @@ impl<T: ?Sized> *mut T {
///
/// This operation itself is always safe, but using the resulting pointer is not.
///
/// The resulting pointer remains attached to the same allocated object that `self` points to.
/// It may *not* be used to access a different allocated object. Note that in Rust, every
/// (stack-allocated) variable is considered a separate allocated object.
/// The resulting pointer "remembers" the [allocated object] that `self` points to; it may not
/// be used to read or write other allocated objects.
///
/// In other words, `let z = x.wrapping_add((y as usize) - (x as usize))` does *not* make `z`
/// the same as `y` even if we assume `T` has size `1` and there is no overflow: `z` is still
@ -721,10 +716,8 @@ impl<T: ?Sized> *mut T {
/// `x.wrapping_add(o).wrapping_sub(o)` is always the same as `x`. In other words, leaving the
/// allocated object and then re-entering it later is permitted.
///
/// If you need to cross object boundaries, cast the pointer to an integer and
/// do the arithmetic there.
///
/// [`add`]: #method.add
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -766,9 +759,8 @@ impl<T: ?Sized> *mut T {
///
/// This operation itself is always safe, but using the resulting pointer is not.
///
/// The resulting pointer remains attached to the same allocated object that `self` points to.
/// It may *not* be used to access a different allocated object. Note that in Rust, every
/// (stack-allocated) variable is considered a separate allocated object.
/// The resulting pointer "remembers" the [allocated object] that `self` points to; it may not
/// be used to read or write other allocated objects.
///
/// In other words, `let z = x.wrapping_sub((x as usize) - (y as usize))` does *not* make `z`
/// the same as `y` even if we assume `T` has size `1` and there is no overflow: `z` is still
@ -786,10 +778,8 @@ impl<T: ?Sized> *mut T {
/// `x.wrapping_add(o).wrapping_sub(o)` is always the same as `x`. In other words, leaving the
/// allocated object and then re-entering it later is permitted.
///
/// If you need to cross object boundaries, cast the pointer to an integer and
/// do the arithmetic there.
///
/// [`sub`]: #method.sub
/// [allocated object]: crate::ptr#allocated-object
///
/// # Examples
///
@ -1261,7 +1251,7 @@ impl<T> *mut [T] {
/// * The pointer must be [valid] for reads for `ptr.len() * mem::size_of::<T>()` many bytes,
/// and it must be properly aligned. This means in particular:
///
/// * The entire memory range of this slice must be contained within a single allocated object!
/// * The entire memory range of this slice must be contained within a single [allocated object]!
/// Slices can never span across multiple allocated objects.
///
/// * The pointer must be aligned even for zero-length slices. One
@ -1283,6 +1273,7 @@ impl<T> *mut [T] {
/// See also [`slice::from_raw_parts`][].
///
/// [valid]: crate::ptr#safety
/// [allocated object]: crate::ptr#allocated-object
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
pub unsafe fn as_uninit_slice<'a>(self) -> Option<&'a [MaybeUninit<T>]> {
@ -1311,7 +1302,7 @@ impl<T> *mut [T] {
/// * The pointer must be [valid] for reads and writes for `ptr.len() * mem::size_of::<T>()`
/// many bytes, and it must be properly aligned. This means in particular:
///
/// * The entire memory range of this slice must be contained within a single allocated object!
/// * The entire memory range of this slice must be contained within a single [allocated object]!
/// Slices can never span across multiple allocated objects.
///
/// * The pointer must be aligned even for zero-length slices. One
@ -1333,6 +1324,7 @@ impl<T> *mut [T] {
/// See also [`slice::from_raw_parts_mut`][].
///
/// [valid]: crate::ptr#safety
/// [allocated object]: crate::ptr#allocated-object
#[inline]
#[unstable(feature = "ptr_as_uninit", issue = "75402")]
pub unsafe fn as_uninit_slice_mut<'a>(self) -> Option<&'a mut [MaybeUninit<T>]> {