RFA-582 · Case file with fixtures · Case 554 of 694 · Compiler evidence
Rust Dereference Needs a Pointer-Like Value, Not the Value Itself
Dereference follows an indirection contract. Write down the expression type and remove the star or obtain a real borrow according to the intended ownership.
- 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
- The code assumed an indirection layer that an earlier iterator, pattern, signature, or refactor no longer places in the expression type.
- First discriminating check
- Write down the exact receiver type, then remove the star or obtain and use a real borrow according to the intended ownership boundary.
The unary * operator follows one level of indirection. It cannot reveal an inner value when the expression is already that value. In the failing fixture, attempts has type u32, so *attempts produces E0614.
Start by writing the exact type
Extra or missing stars often appear after a function signature changes, pattern matching automatically dereferences something, or iterator adapters produce references. Guessing from the variable name is unreliable. I write down the expression's compiler-known type at the failing location.
If it is u32, I can use it directly. If it is &u32, one dereference produces a place containing u32. If it is &&u32, another layer exists. For generic or smart-pointer types, Deref may participate.
The official E0614 explanation shows the direct distinction between a number and a reference to the number. This is about type structure, not whether the value resides somewhere in physical memory.
The fixture repair demonstrates real indirection
The repaired fixture creates borrowed = &attempts and then reads *borrowed. This is educational evidence, not a recommendation to add a borrow merely to keep the star. In the original program the simplest repair would be to remove *.
I choose based on the wider ownership contract. If a function should observe without taking ownership, it may accept a reference and dereference or rely on method auto-deref. If the caller already owns a Copy number, direct use is clearer. For a non-Copy value, dereferencing a shared reference does not permit moving the inner value out.
Deref is a constrained abstraction
User-defined pointer-like types can implement Deref. Box<T>, Rc<T>, and Arc<T> use this mechanism to provide access to their targets. The compiler also uses deref coercions in selected contexts, such as converting &String to &str.
I do not implement Deref on every wrapper to make inner methods appear. Deref participates implicitly in method resolution and should represent transparent pointer-like access. A domain wrapper such as UserId usually benefits from explicit methods rather than pretending to be a pointer to its integer representation.
The Book's Deref chapter explains the ergonomic behaviour, but the safety boundary remains important: implementing DerefMut exposes mutable target access and may let callers violate wrapper invariants.
Patterns and iterators can change reference depth
Calling .iter() yields references to items, while .into_iter() often yields owned items depending on the receiver. Matching for &number in slice destructures each reference and binds a copied number. Then adding *number is one dereference too many.
Conversely, filter callbacks often receive an extra reference because the iterator must retain its item. Closure pattern ergonomics can hide this detail until a type error appears. I inspect the adapter's trait signature and use a temporary explicit closure type or binding.
References to mutable values add another concern. *slot = new_value needs a mutable place reached through &mut or an appropriate DerefMut implementation. A shared reference cannot become mutable through another star.
Raw pointers require an unsafe proof
Raw pointers support dereference only inside unsafe, and E0614 should never be “fixed” by casting an ordinary value to a pointer. Before dereferencing a raw pointer, the program must prove non-nullness where required, alignment, validity for the target type, initialisation, aliasing rules, and lifetime for the access.
I isolate that proof at an FFI or systems boundary and expose safe references or owned values after validation. Number-to-pointer casts can compile in some forms while being entirely unrelated to the intended data.
My E0614 checklist
- What is the exact type immediately before the
*operator? - Is the expression already the desired value?
- Which operation introduced or removed a reference layer?
- Is moving out allowed, or should the code copy, clone, or borrow?
- Does a smart pointer legitimately implement
Deref? - Would explicit domain methods be safer than a new Deref implementation?
- For mutation, is there a real mutable place and
DerefMutaccess? - If a raw pointer is involved, where is every validity obligation proved?
The core principle is that dereference is not “unwrap anything.” It follows a declared pointer-like relationship. Once I make the current type and ownership visible, E0614 usually becomes a small syntax fix and a useful check on the API boundary.