Rust Pointer Provenance: What addr(), with_addr() and Miri Change About as usize
Software / explainer
Rust Pointer Provenance: What addr(), with_addr() and Miri Change About as usize
A Nov. 2 post and the standard library docs show what a pointer-to-integer cast throws away, and the Rust APIs that avoid it.
A pointer in Rust is two things, an address and a permission, and casting it to a usize keeps only the first. A Nov. 2 post by Sofía Belén López Vicens walks through what that costs in Rust and C++, with the compiler output to show it.
What provenance is
The Rust standard library documentation says a pointer value semantically contains the address it points to and "the provenance it has, defining the memory it has permission to access." Provenance can be absent, in which case the pointer has permission to access nothing. The docs conclude that "a usize cannot accurately represent a pointer," and that converting a pointer to an integer "only extracts the address."
López Vicens calls provenance "purely a compiler abstraction": it is not stored at runtime, with the CHERI architecture as the hardware exception. Its practical use is optimization. In her C++ example, a call to an opaque function opaque(p) cannot modify a local x, because the compiler assumes p "wasn't derived from x," so it can assume x is unchanged after the call.
Where the integer cast breaks the assumption
Casting &x with reinterpret_cast<std::uintptr_t> before handing it to another function, her post shows, "exposes" the address, and the compiler (Clang 23.1.0 on Godbolt, inspected through LLVM IR) has to assume that anything may now point at x. The optimization disappears.
Rust offers three routes out of that trap, and the docs name them. Strict Provenance uses addr() to read an address and with_addr() to build a new pointer "with the given address and the provenance of self." Exposed Provenance uses expose_provenance() and with_exposed_provenance(), which the docs say are equivalent to as casts between pointers and integers. And without_provenance() makes a pointer with none, which is dangling by design.
| API | What it does | Provenance effect |
|---|---|---|
p.addr() | Reads the address | Promises no round trip back to a pointer |
p.with_addr(a) | New pointer at a | Copies provenance from p |
p.expose_provenance() | Reads the address | Adds provenance to a conceptual global list |
with_exposed_provenance(a) | Pointer from an integer | Compiler picks from exposed list, no guarantee |
without_provenance(a) | Pointer from an integer | None; dereferencing is undefined behavior |
The tagged-pointer example
The standard use case is pointer tagging, hiding a flag in the low bit of an aligned pointer. López Vicens writes it as p.with_addr(p.addr() | 1) and clears it with tagged.with_addr(tagged.addr() & !1). Because the new pointer inherits provenance from p, nothing is exposed. The standard library's version uses map_addr for the same job, with a u32 and a HAS_DATA constant of 0x1.
The tagged pointer is misaligned, and her post notes that dereferencing it would be undefined behavior. Only the cleared pointer can be read.
What Miri says about the old way
The legacy route still works. Casting with p as usize and back "runs correctly" in her experiment, because the compiler recovers the provenance. But Miri, Rust's interpreter for detecting undefined behavior, warns that it may miss bugs and recommends with_exposed_provenance or the Strict Provenance APIs, and names the -Zmiri-strict-provenance and -Zmiri-permissive-provenance flags. The docs agree, saying Exposed Provenance "will not work (well) with tools like Miri and CHERI," and that it is "on much less solid footing than Strict Provenance."
What the experiment does not prove
The author is plain about a weakness. In her experiment addr() still gave up the optimization, so the benefit today is room for future compiler work and better Miri checking, and the Rust version she tested was unoptimized. The post names no rustc version.
The Rust docs add a larger caveat: "the full definition of provenance in Rust is not decided yet." They do not state which Rust release stabilized each method, and neither does the post, so check the stable attribute on each method's own page before putting them in a crate with a minimum supported Rust version.
For code that stores flags in pointers today, the practical change is small: swap as usize for addr() and with_addr(), run Miri with -Zmiri-strict-provenance, and see which lines it rejects.
Related: BenchUp, the Rust SketchUp alternative and the Node.js 27 alpha are other recent systems-level software stories.
Sources
More in Software
- 01Git Contributors Draft a Debian-Style Vote on Whether to Keep the Ban on AI-Looking PatchesThe project has not agreed who may vote, how ballots are counted or what the options are, and the policy under dispute rejects any patch that looks AI generated.
- 02.NET 11 Ships With C# 15 Union Types, the Same Day .NET 8 and 9 Lose SupportMicrosoft's release is a two-year Standard Term Support build whose end-of-support date matches .NET 10's, which changes the upgrade maths for teams still on .NET 8.
- 03Agent Skills: Why Matt Pocock's 27-Skill Repo Has More Stars Than Anthropic'sBoth repositories are on GitHub trending. They share one file format, differ in intent, and one of them tells you in its README not to rely on it.
- 04Sherlock 0.16.2 Writes Reports Outside Its Folder on a ../ UsernameTwo unmerged pull requests would fix the bug, and the last two gaps between Sherlock releases were 435 and 359 days.