Pointer APIs and Provenance

Purpose and Scope

This page is a focused reference for developers reading or using Rust raw pointer APIs in core. Raw pointers are values such as *const T and *mut T; they can represent addresses without automatically carrying Rust reference guarantees. The APIs covered here sit at the boundary between address arithmetic, null handling, and conversion from pointers into references. That boundary is intentionally strict because a small mistake can turn ordinary pointer manipulation into undefined behavior, especially when provenance, allocation bounds, or initialization are misunderstood.

The documentation snippets in library/core/src/ptr/docs are not standalone user chapters. They are shared method documentation fragments used to avoid duplicating equivalent text between mutable and immutable pointer methods. The repository explicitly warns contributors that most files in this directory are partial method docs, because examples, links, and mutable-return variants often differ between *const T and *mut T. When changing or auditing these files, the practical rule is to review the rendered documentation, not only the fragment, so sections are not accidentally split across generated pages.

Sources: library/core/src/ptr/docs/INFO.md

The central concept for this group is provenance. In Rust pointer documentation, provenance is the information that connects a pointer to the allocation it came from and therefore to the memory it may be used to access. An integer address by itself is not enough to justify dereferencing. Methods such as addr, add, sub, offset, is_null, as_ref, and as_uninit_ref all describe some combination of address values, allocation membership, reference conversion, const-evaluation behavior, and initialization state. Reading them together helps avoid the common mistake of treating a raw pointer as only a machine integer.

Sources: library/core/src/ptr/docs/addr.md, library/core/src/ptr/docs/add.md, library/core/src/ptr/docs/sub.md, library/core/src/ptr/docs/offset.md, library/core/src/ptr/docs/is_null.md, library/core/src/ptr/docs/as_ref.md, library/core/src/ptr/docs/as_uninit_ref.md

Relevant Source Files

  • library/core/src/ptr/docs/INFO.md - explains why pointer method documentation is factored into shared fragments and why rendered docs must be manually reviewed after edits.
  • library/core/src/ptr/docs/addr.md - documents extracting the address portion of a pointer while discarding provenance under the Strict Provenance model.
  • library/core/src/ptr/docs/add.md - documents unsigned forward pointer movement, including unit-of-T counts and safety requirements for allocation bounds.
  • library/core/src/ptr/docs/sub.md - documents unsigned backward pointer movement with the same mathematical-integer and in-bounds allocation constraints.
  • library/core/src/ptr/docs/offset.md - documents signed pointer movement for forward or backward offsets and describes the combined range that must remain in bounds.
  • library/core/src/ptr/docs/is_null.md - documents null checking, including behavior for unsized values and possible panics during const evaluation.
  • library/core/src/ptr/docs/as_ref.md - documents converting a raw pointer into Option<&T> when the pointer is null or convertible to a reference.
  • library/core/src/ptr/docs/as_uninit_ref.md - documents converting a raw pointer into Option<&MaybeUninit<T>> without requiring the pointee to be initialized.
  • library/core/src/ptr/docs/as_uninit_slice.md - provides the requested source location for slice-oriented uninitialized pointer reference documentation in the same shared docs family.

Core Concepts

addr is the cleanest entry point for understanding the difference between an address and a usable pointer. It returns the address portion of a pointer, similar in surface shape to casting a pointer to usize, but the documentation is careful about what is lost. The returned integer does not preserve the pointer provenance and does not expose provenance. If code later casts that address back to a pointer, the result is a pointer without provenance, and dereferencing it is undefined behavior. To recover a dereferenceable pointer after address transformation, the documented path is to use APIs such as with_addr or map_addr, which preserve the original provenance through the transformation.

Sources: library/core/src/ptr/docs/addr.md

The addr documentation also explains why Strict Provenance is a portability and tooling contract rather than merely a runtime implementation detail. On most platforms the address value may have the same bytes as the pointer, because the pointer representation is just an address. On other platforms, however, a pointer may contain additional information, and extracting only the address may require a representation change. Code that falls back to pointer-integer casts or exposed-provenance APIs may be necessary for some low-level designs, but the documentation warns that this is less portable and less suitable for tools that check the Rust memory model.

Sources: library/core/src/ptr/docs/addr.md

Pointer arithmetic APIs express movement in units of T, not raw bytes. For add, sub, and offset, a count of 3 means 3 * size_of::<T>() bytes. This matters because the safety rules are written using mathematical integers rather than wrapping machine arithmetic. The byte offset must fit in isize, and the resulting address calculation must fit in usize. These requirements prevent a caller from justifying arithmetic by relying on overflow behavior that would not be valid in the abstract memory model.

Sources: library/core/src/ptr/docs/add.md, library/core/src/ptr/docs/sub.md, library/core/src/ptr/docs/offset.md

The directional difference between the arithmetic methods is small but important. add can move forward or stay in place, sub can move backward or stay in place, and offset accepts a signed count so it can move either direction. All three tie non-zero movement to an allocation: the starting pointer must be derived from a pointer to some allocation, and the entire memory range between the start and result must be in bounds of that allocation. For add, that range is from the starting address to the result; for sub, it is from the result up to the starting address; for offset, it is the minimum-to-maximum range covering either direction.

Sources: library/core/src/ptr/docs/add.md, library/core/src/ptr/docs/sub.md, library/core/src/ptr/docs/offset.md

API Reference

APIPrimary behaviorKey safety or semantic point
addrGets the address portion of a pointer.Discards provenance; casting the address back produces a pointer without provenance unless provenance is restored through appropriate APIs.
add(count)Moves a pointer forward by count * size_of::<T>() bytes.Non-zero movement must stay within the allocation from which the pointer is derived.
sub(count)Moves a pointer backward by count * size_of::<T>() bytes.The computed backward range must remain within the same allocation.
offset(count)Moves a pointer by a signed count in units of T.The range between the original and result pointer must be in bounds for either direction.
is_nullReturns whether the raw data pointer is null.For unsized types, metadata such as length or vtable is not part of nullness.
as_refReturns None for null, otherwise Some(&T).Caller must ensure the pointer is null or convertible to a reference, and initialized data is required.
as_uninit_refReturns None for null, otherwise Some(&MaybeUninit<T>).Allows uninitialized pointees while still requiring null-or-reference-convertible pointer validity.
as_uninit_sliceSlice-oriented uninitialized reference documentation source.Review with rendered pointer docs and the uninitialized-reference family before relying on slice behavior.

The arithmetic rules include a useful consequence for common collection code: allocations can never be larger than isize::MAX bytes and can only contain addresses representable by usize. Because of that, the in-bounds allocation requirement implies the integer-fitting requirements in ordinary valid allocations. The docs call out examples such as computing a one-past-the-end pointer from a vector with vec.as_ptr().add(vec.len()) or vec.as_ptr().offset(vec.len() as isize). Those examples are not blanket permission for arbitrary dereference; they show that forming the one-past pointer is safe under the documented allocation conditions.

Sources: library/core/src/ptr/docs/add.md, library/core/src/ptr/docs/offset.md

Null Checks and Reference Conversion

is_null checks only the raw data pointer. That distinction is especially important for unsized types, because a fat pointer can include metadata such as a slice length or trait-object vtable. The documentation says unsized types have many possible null pointers because only the data pointer is considered. As a result, two pointers can both be null according to is_null while still not comparing equal to each other, since their metadata can differ. This is a precise reminder that nullness and full pointer equality are different questions.

Sources: library/core/src/ptr/docs/is_null.md

Const evaluation adds another constraint. During const evaluation, the compiler may not know an absolute runtime address. If is_null is called on a pointer that has been offset beyond the bounds of its original memory, there may not be enough information to determine nullness, and the method can panic. The docs also give the positive case: in-bounds pointers are never null, so the method will not panic for such pointers. This matters for APIs that internally need a null check before building an optional reference.

Sources: library/core/src/ptr/docs/is_null.md

as_ref builds directly on the null-checking model. It returns None when the pointer is null, and otherwise returns a shared reference wrapped in Some. The method is still unsafe because the caller must guarantee that the pointer is either null or convertible to a reference. That conversion requirement is stronger than simply having a non-zero address; it includes the validity rules that make a Rust reference legal. The docs also direct callers to as_uninit_ref when the value may be uninitialized and to an unchecked variant when null has already been ruled out.

Sources: library/core/src/ptr/docs/as_ref.md

as_uninit_ref has the same optional-reference shape but changes the initialization requirement. Instead of producing &T, it produces a reference to MaybeUninit<T>, so the source pointer may point at uninitialized memory. That does not remove the pointer-validity requirement: the pointer must still be null or convertible to a reference. In practice, this is the distinction to keep in mind when implementing low-level initialization routines. Use the uninitialized-reference APIs for memory that is allocated and reference-convertible but not yet initialized as a T; do not use as_ref until initialization is known.

Sources: library/core/src/ptr/docs/as_uninit_ref.md

Implementation and Documentation Workflow

Because these files are shared snippets, the source-to-rendered-doc workflow is part of the API contract. The same fragment can appear under immutable and mutable pointer methods, but links and examples may differ. INFO.md gives concrete reasons: examples must call the correct mutable or immutable method, link reference definitions often point to distinct *const T and *mut T pages, and mutable pointer methods may link to alternate APIs that return mutable references. A change that looks safe in the fragment can therefore produce a confusing rendered section if it lands in the wrong generated context.

Sources: library/core/src/ptr/docs/INFO.md

When editing these docs, start by deciding which guarantee is being described: address extraction, provenance preservation, allocation-bounded arithmetic, nullness, reference conversion, or initialization state. Then check whether the wording applies equally to *const T and *mut T. If it does, the shared file is appropriate; if it needs different examples, links, or return-type language, the rendered output must be reviewed carefully. For user-facing guidance, keep the first rule simple: forming a pointer, checking a pointer, and dereferencing through a reference are separate operations with separate obligations.

Practical Guidance and Next Steps

For everyday unsafe code review, read these APIs in a fixed order. First ask whether the code is preserving provenance or throwing it away through integer-like address handling. Next check whether any arithmetic stays within a single allocation and uses counts in units of T. Then inspect null handling, remembering that unsized pointer metadata does not control nullness. Finally, verify whether the code is creating &T or &MaybeUninit<T> and whether the pointee is actually initialized. This order mirrors the documented responsibilities and catches most mismatches before they become memory-model bugs.

If you are learning rather than editing the standard library docs, pair this page with the broader standard-library and unsafe-Rust material. The official Rust learning path treats the standard library docs as the comprehensive API reference and the Rustonomicon as the guide to unsafe Rust’s darker corners. In this repository, the immediate next step is to inspect the rendered core::ptr docs for the exact method receiver, mutability, examples, and links produced from these shared fragments. For source work, review library/core/src/ptr/docs/INFO.md before modifying any pointer method documentation.