Mehdi Akiki
Rust Failure Atlas / Runtime, memory, and library APIs

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.