Rust 1.84 makes pointer provenance explicit
Strict provenance APIs distinguish an address from permission to access an allocation. Rust 1.84 also lets Cargo consider a package's supported compiler version when selecting dependencies.
Two pointers can have the same numeric address without granting the same permission to access memory. Reallocating storage at an old address does not make a pointer to a freed allocation valid again. That distinction is difficult to express when low-level code repeatedly converts pointers to integers and reconstructs pointers from the resulting numbers.
Rust 1.84, released on January 9, stabilizes strict provenance APIs that make the derivation of a pointer explicit. It also adds Cargo's minimum-supported-Rust-version-aware dependency resolver. Both changes give tools information that a bare address or a version requirement alone cannot express.
I am drawn to Rust's ability to state low-level obligations precisely. Go remains my first choice for many ordinary APIs and command-line programs, but Rust is especially compelling where C would otherwise be the starting point. These pointer APIs address the sort of detail that determines whether an unsafe abstraction has a defensible contract.
An address is one component of a pointer
The Rust 1.84 pointer documentation describes provenance as permission to access memory. That permission has spatial, temporal, and mutability components. A pointer can have the right address and still lack permission because it came from the wrong allocation or outlived the allocation that supplied it.
This model explains why a use-after-free remains invalid even if a later allocation occupies the same location. The old pointer's derivation concerns an allocation whose lifetime ended. Comparing its address with a new pointer does not transfer the new allocation's permission to it.
It also explains why pointer arithmetic must respect more than integer arithmetic. An address calculation that does not overflow can still escape the allocation associated with a pointer. A low-level abstraction must establish both the intended numeric result and the validity conditions for the operation that uses it.
The documentation explicitly says that Rust's complete provenance and aliasing model remains unsettled. Stabilizing these APIs does not claim that every question about unsafe memory has a final specification. It gives programmers operations designed to avoid the ambiguity of reconstructing access permission from an integer alone.
Keep a pointer while changing its address
The raw pointer methods provide addr, with_addr, and map_addr. addr obtains the numeric address without exposing provenance. with_addr supplies another address while retaining the provenance of the original pointer. map_addr applies an address transformation with the same retained provenance.
The distinction matters for pointer tagging. An aligned allocation can leave low address bits available for metadata while the pointer is stored. A tagged value is not necessarily suitable for dereferencing. The code must remove the tag and restore a valid address before performing an access.
let value = 42_u32;
assert!(std::mem::align_of::<u32>() > 1);
let original = &value as *const u32;
let tagged = original.map_addr(|address| address | 1);
let restored = tagged.map_addr(|address| address & !1);
assert_eq!(restored.addr(), original.addr());
This fragment demonstrates address transformation without reading through either raw pointer. The alignment assertion establishes that the original address has the spare low bit the example uses. The restored pointer retains the original derivation because both transformations operate on pointers rather than synthesizing access permission from a standalone integer.
The absence of a dereference is deliberate. Constructing or manipulating a raw pointer is distinct from proving it can be used for an access. A production tagged-pointer abstraction still has to preserve allocation lifetime and the permissions required by every eventual read or write.
The operation does not enlarge permission
with_addr can produce an address outside the original allocation, just as wrapping pointer arithmetic can. That does not grant access to the allocation at the new address. The retained provenance still belongs to the pointer used as the receiver. A safe method for computing a pointer value is not a guarantee that every later unsafe operation on it is valid.
This limits what an integer calculation can accomplish. Keeping a pointer into one object and changing its address to that of another does not combine the objects' allocations. The abstraction needs a pointer with the relevant provenance for the memory it intends to access.
That restriction is useful to a reviewer. The code identifies which pointer supplies the permission separately from the expression computing the address. A unary integer-to-pointer cast cannot provide that same local relationship because it names only the number. The explicit receiver gives tools and readers something concrete to follow through the program.
The method implementation uses wrapping offsets internally to preserve provenance. Callers should depend on the documented method contract, not reproduce implementation details with transmutation. The source itself warns that some internal operations rely on the special status of standard-library code.
Exposed provenance remains a separate mechanism
Rust 1.84 also stabilizes named exposed-provenance operations. expose_provenance corresponds to exposing a pointer through an integer conversion, and with_exposed_provenance reconstructs a pointer using the exposed-provenance model. These are different operations from extracting an address for a strict-provenance transformation.
The documentation acknowledges cases involving legacy platform interfaces and fixed memory-mapped locations where strict-provenance APIs are insufficient. Keeping a pointer into the relevant allocation is straightforward for many tagging patterns, but not for every interface a systems language must support. Naming exposed provenance makes that choice visible instead of hiding it inside an unremarkable cast.
This is not a recommendation to replace every pointer cast mechanically with a single preferred method. A migration needs to identify whether code merely inspects an address, modifies an address while retaining an allocation, or depends on reconstructing a pointer through exposure. Those operations have different meanings even if their numeric results look identical on an ordinary machine.
I prefer APIs that force that distinction into the code. An unsafe abstraction becomes easier to understand when its pointer derivation is explicit. The benefit is precision in the proof, not a promise that raw-pointer code has become safe merely by using a recently stabilized method.
Cargo also needs more than a version range
The release's dependency resolver addresses a different missing piece of information. A semver-compatible library version can require a newer compiler than the consuming package supports. Selecting the newest dependency under the version range alone can therefore violate the consumer's intended compiler compatibility.
Rust 1.84 lets Cargo prefer dependency versions compatible with the package's declared rust-version. The release announcement gives an opt-in setting in .cargo/config.toml:
[resolver]
incompatible-rust-versions = "fallback"
The word fallback matters because this is a preference used during resolution. It is not a proof that an arbitrary dependency graph will compile on every compiler a maintainer hopes to support. Dependency metadata and compatible available versions still determine what the resolver can select.
The announcement also documents resolver version 3 as another opt-in that requires Rust 1.84. A package adopting the new resolver changes its own toolchain requirement, so the resolver choice must not be confused with a mechanism for making new Cargo syntax usable by older Cargo releases.
Tooling benefits from stated assumptions
The release also begins using the next-generation trait solver for coherence checks. Coherence asks whether trait implementations can overlap, including in the presence of code from other crates. This is narrower than switching every trait-solving operation to the new implementation. The announcement identifies this stage explicitly so that readers do not mistake a partial migration for a complete compiler replacement.
Some previously accepted implementation combinations can now produce conflicting-implementation errors, while other combinations become provably disjoint. Both effects follow from changing the compiler's reasoning about the same obligations. A diagnostic is not necessarily a new language restriction if the previous solver accepted a case incorrectly. Conversely, proving that two implementations cannot overlap can permit code that the earlier solver could not justify.
This compiler work shares the release's concern with making rules useful to tools. Source syntax describes implementations, but the solver must establish their relationships under Rust's coherence requirements. The release announcement reports that evaluation with Crater found affected patterns to be rare, without claiming that every downstream codebase has been examined.
The pointer APIs make allocation derivation explicit for memory analysis, while dependency metadata lets Cargo account for compiler compatibility during version selection. Neither mechanism can supply an assumption that the program or package never stated. An address alone does not identify access permission, and a dependency range alone does not establish its compiler requirement.
For library authors, the practical result is a more specific contract. Unsafe code can retain and transform pointers without unnecessary integer round trips. A package can declare its supported compiler and let the resolver use that declaration when selecting dependencies. The remaining obligations stay visible, including valid memory accesses and actual compilation on the supported toolchain.