Over the years, there has been ample discussion on the concept of a "&move
"/"&own
"/"move reference"/"owning reference". However, its raw pointer cousin has been comparatively neglected. This topic aims to correct that shortfall.
For &
(reference to value you don't own and can't modify), we have *const
. For &mut
(reference to value you don't own but can modify), we have *mut
. But for Box
, Vec
, Arc
, and all the other safe-code references to values you own… we have, erm, NonNull
? Except that's not a real pointer type, so you have to call .as_ptr()
before using it (and no convenient coercions!). To construct it, you need new().unwrap()
(the concerns of "pointer to value you own" and "pointer guaranteed to be non-null" are sadly not separated).
In practice, when writing unsafe code to manage owned memory, one must use an awkward mix of NonNull
(correctly represents semantic intent, but inconvenient to use in practice), *const
(a real raw pointer type, and with the correct variance, but you can't write to it, and it conveys the wrong semantic intent), and *mut
(a real raw pointer you can write to, but with the wrong variance, and semantic intent not fully correct). Unsafe code is tricky enough without this extra hassle; can we do better?
*move
(*own
?) would be a raw pointer type with the following characteristics:
- Covariant
- Allows writes
- Implicitly coerces to
*mut
and*const
- Constructed from a place via
addr_of_move!(place)
, which requires the place to be something you can move out of (soaddr_of_move!(*(&mut foo))
doesn't work for example). This macro would in fact logically perform such a move; the place would no longer be considered accessible by the borrow checker, anddrop_in_place
would not be called on it.- No construction via coercion from
&
or&mut
- As with the other pointer types, can also be constructed via cast from a pointer with different mutability
- No construction via coercion from
- Basis for
NonNull
- The raw pointer type of choice for dealing with owned memory
- Backward compatibility will be an issue, the standard library is full of APIs like
Box::into_raw()
that use*mut
as a substitute for*move
.
- Backward compatibility will be an issue, the standard library is full of APIs like
In my Notes on partial borrows a few months ago, I observed that if &mut T
were to be made a subtype of &T
(as it is logically), Rust's variance rules would need to handle such mutability-based subtyping differently from lifetime-based subtyping. This is because &&mut T
is logically a supertype of &&T
; &T
is contravariant with respect to the mutability of T
. However, Box<T>
is logically covariant with respect to T
's mutability, just as it is with respect to T
's lifetime. In a hypothetical Rust with both mutability variance and *move
, *const T
would be contravariant[1] with respect to T
s mutability, but *move T
would be covariant. In my view, this further demonstrates that *move
is a distinct concept that deserves its own type.
in practice it would probably be invariant, due to backward compatibility concerns ↩︎