RFA-594 · Case file with fixtures · Case 566 of 694 · Compiler evidence
Rust Raw Pointer Casts Need a Concrete Pointee Type
A pointer address alone does not define pointer meaning. Specify the pointee type, then separately prove provenance and validity before any dereference.
- 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
- A numeric address was treated as a complete pointer value even though pointee type governs arithmetic, alignment, and valid dereference.
- First discriminating check
- Identify the real foreign object or byte region and state its pointee type explicitly, then prove pointer validity separately.
A raw pointer type includes mutability and a pointee type. Writing *const _ asks inference to determine the pointee from context. In the failing fixture, an integer zero provides no such context, so rustc reports E0641.
The pointee type controls operations
*const u8 and *const Header may carry the same numeric address on a target, but they do not mean the same thing. Pointer arithmetic scales by pointee size, alignment requirements differ, and dereference validity depends on a properly initialised value of that type.
The official E0641 page shows that type information can appear directly in the cast or in the destination binding. Inference works when the source reference already establishes a concrete type.
I do not choose u8 merely because it is easy. I identify what allocation, foreign object, or byte region the address is intended to denote.
The fixture repair is typed but deliberately not dereferenced
The repaired fixture casts zero to *const u8 and checks is_null. This demonstrates type resolution only. It does not make the null pointer dereferenceable.
In ordinary code I prefer std::ptr::null or null_mut when a C API requires a sentinel. The expected pointer type still needs to be known, and using the pointer constructor communicates intent better than an integer cast.
For optional Rust references, Option<&T> is normally safer and can use efficient representation. Raw null pointers belong at FFI, allocator, or low-level data-structure boundaries where their semantics are explicit.
A pointer type does not prove pointer validity
After E0641 is fixed, dereferencing remains unsafe. The program must establish a valid address for the access, correct alignment, initialisation, an in-bounds allocation, valid pointee representation, aliasing permissions, and a lifetime covering the operation.
Typing a random address as *const Header proves none of these. Nor does a successful null check prove that a non-null pointer is valid. I keep raw pointers inside small abstractions that validate requirements and return safe references only for a justified scope.
Rust's raw pointer reference explains their syntax and weaker safety guarantees. Raw pointers can be copied and may be null or dangling; safe code cannot dereference them.
Addresses and provenance are not freely interchangeable
Modern Rust memory reasoning distinguishes an address from the provenance needed to access an allocation. Integer-to-pointer operations are not a portable replacement for deriving pointers from allocated objects. Strict-provenance APIs make address manipulation intent more explicit.
For memory-mapped devices and operating-system interfaces, integer addresses can be legitimate under a platform contract. I document that contract, use volatile operations where required, and test on the actual target. This exceptional case should not shape normal application pointer code.
FFI bindings should use the precise foreign pointee type or an opaque type rather than u8 everywhere. Opaque handles are not byte buffers, and treating them as such invites arithmetic and dereference mistakes.
Inference failures can reveal a missing API type
If a generic function passes untyped null to several APIs, annotating each call may be noisy. A typed helper or wrapper can establish the foreign handle type once. This reduces casts and makes ownership functions such as create, borrow, and destroy line up.
I also distinguish *const T from *mut T. The latter means raw mutable capability, not necessarily unique access by itself. Converting const to mut does not grant permission to write.
My E0641 checklist
- What concrete object or byte region should the pointer denote?
- Where can the pointee type be stated most clearly?
- Is a typed null constructor better than an integer cast?
- Could
Option<&T>or another safe type represent absence internally? - Who proves alignment, initialisation, bounds, aliasing, and lifetime?
- Does integer address conversion have an explicit platform contract?
- Is an opaque foreign handle being mistaken for a byte pointer?
- Does mutability match the actual permission rather than only the desired operation?
The core principle is that an address is incomplete without type and access history. E0641 asks for the missing pointee kind. I supply it from the real boundary, then keep the much larger unsafe validity proof visible and contained.