RFA-410 · Case file with fixtures · Case 382 of 694 · Runtime evidence
ptr::eq on Slices Compares Length Metadata Too
Slice references are wide pointers containing a data address and length metadata. std::ptr::eq compares the complete pointer, while ptr::addr_eq deliberately compares only addresses after ignoring metadata.
- Reviewed
- Rust
- Rust 1.98.1, edition 2024
- Targets
- all Rust targets
- Profiles
- dev, release, test
Direct answer
What this Rust failure means
- Why it happens
- Slice references are wide pointers and ptr::eq compares both the data address and pointer metadata; addr_eq deliberately ignores metadata when only allocation position matters.
- First discriminating check
- Compare addresses and metadata as separate facts, using addr_eq for the former and lengths or the full wide-pointer equality for the latter.
I had two slices beginning at the same array element and expected std::ptr::eq to call them equal. It did not, because a slice pointer carries more than an address.
The failing fixture compares a four-byte slice with its two-byte prefix. Their as_ptr() values match, but ptr::eq(whole, prefix) is false.
A slice reference is a wide pointer
An ordinary reference to a sized value can be represented by its data address. A reference to [T] also needs the runtime number of elements. That length is pointer metadata.
Conceptually:
&[T] = (data address, element count)
The whole slice in the fixture is (address, 4). The prefix is (same address, 2). They begin at one location but describe different dynamically sized values.
std::ptr::eq compares pointers for equality, including metadata for wide pointers. Both components must match here.
Address equality asks a narrower question
std::ptr::addr_eq ignores metadata and compares only the data addresses. The repaired fixture asserts:
assert!(std::ptr::addr_eq(whole, prefix));
assert!(!std::ptr::eq(whole, prefix));
assert_ne!(whole.len(), prefix.len());
These are not contradictory assertions. They answer different questions:
- Do both views start at the same address?
- Do both pointers describe the same complete dynamically sized value?
I select the function by the identity claim the application needs.
Same address does not mean same accessible range
The prefix cannot safely access elements two and three merely because another slice starting at the same address can. Bounds belong to the reference value. Metadata is part of the capability that safe indexing uses.
This matters when caching parsed views, checking aliasing, or comparing buffer regions. Two views can share a start and have different ends. Address-only equality is too weak to prove equal range.
For range identity I compare start plus length, or start and end addresses after carefully keeping units and provenance. The full slice pointer equality already captures start and length for the same element type.
Same range does not prove interchangeable meaning
Even complete pointer equality only establishes pointer identity under the API's rules. It does not prove that two domain objects have the same permissions, protocol state, or interpretation.
A byte slice may be viewed once as encoded text and once as a checksum payload. The pointer is equal; the application contract is not necessarily the same.
I keep semantic identity separate from storage identity. Pointer comparisons are low-level evidence, not a replacement for domain keys.
Trait objects have different metadata
Wide pointers also represent trait objects, where metadata identifies dynamic dispatch information rather than a slice length. The ptr::eq documentation warns that comparing trait-object pointers can produce surprising results because code generation may duplicate or deduplicate vtables.
I do not use trait-object pointer equality as a universal dynamic-type identity mechanism. TypeId, explicit IDs, or domain identity may be more appropriate depending on the problem.
The slice fixture avoids this ambiguity. Length metadata is directly observable and deterministic.
Metadata has a type-level model
The pointer APIs describe metadata through the Pointee model. Sized pointees have unit metadata, slices have a usize length, and trait objects carry dynamic metadata.
I do not need to manipulate raw metadata for ordinary slice work. Understanding that it exists is enough to explain pointer size, equality, and why casting a wide pointer directly to one integer is restricted.
When unsafe code reconstructs a slice, both the address and the correct length are safety-critical. A valid allocation address paired with an excessive length creates an invalid reference.
Empty slices make address identity especially weak
Empty slices contain no accessible element, yet they still need a non-null, aligned reference representation. Different empty slices may use convenient dangling addresses, and address reuse can occur across allocations over time.
I never use a data address alone as a persistent object ID. Allocators may reuse addresses after lifetimes end, and zero-length views do not establish ownership of bytes.
If the application needs durable identity, I allocate an explicit generation or key.
My comparison checklist
When a wide-pointer comparison surprises me, I inspect:
- the pointee type;
- the data address;
- slice length or other metadata;
- whether both pointers are alive under compatible provenance;
- whether the desired claim is address, range, allocation, or domain identity.
The core principle is that a pointer to an unsized value describes more than where data begins. ptr::eq respects that complete description. addr_eq is available when I intentionally need the narrower address fact, and I avoid turning that fact into a stronger alias or identity proof.