Hey,
is there a way to cast from *mut T to T (in unsafe ofc)?
I wouldn't call this a cast, per se. You have to read the pointer.
If it points to a type that has a destructor, you must be aware that the destructor will run once the output T
falls out of scope. If that T
is also owned somewhere else (wherever you got the *mut T
from), then you'll end up with a drop of uninitialized data when the original storage is dropped.
Thanks to this, calls to std::ptr::read
for generic T
must almost always be paired with some method to leak the original copy of T
.
That is unless you write all your code like this:
*var.foo();
*var.abc = "abc";
*var.this;
*var.that;
(Which would be useful in some cases)
Another case would be to clone the T
(if it is clone), then change it, and then re-set the underlying value.
The function std::ptr::read
always works as I described.
I'm surprised that *x
is even supported for raw pointers, because its contractual obligations with regard to moves and drops are unclear to me. I've always used the following, all of which are functions whose contracts are thoroughly documented:
-
std::ptr::as_ref()
to upgrade*const T
to&T
. -
std::ptr::as_mut()
to upgrade*mut T
to&mut T
. -
std::ptr::read()
to obtain aT
from a*const T
(potentially dropping it). -
std::ptr::write()
to write aT
to a*mut T
(without dropping its existing data).
Don't worry; you can't use *x
to move or drop a T
through a raw pointer to T
. If you try, you'll get a compiler error "cannot move out of dereference of raw pointer."
*x
works for copying the value of a Copy
type, or for getting a borrowed reference to other types by doing &*x
or (*x).some_borrowing_method()
.
You can't do it accidentally, but there is ptr::drop_in_place
to drop through a pointer.
Ah-but you can drop a T
accidentally, by writing to it:
struct Droppable(i32);
impl Drop for Droppable {
fn drop(&mut self) {
println!("dropping {}", self.0);
}
}
fn main() {
let mut x = Droppable(0);
let p: *mut _ = &mut x;
unsafe { *p = Droppable(1); } // prints "dropping 0"
// prints "dropping 1"
}
This is to say, writing to a pointer has the subtle and tricky precondition that the data existing at that location must be safe to drop. This is true for all statements of the form *p = x
regardless of the type of p
, but one doesn't generally think about it because it only *mut T
can violate this precondition.
I suppose worth mentioning too that the following doesn’t suppress drop of the 0 value, as one might naively expect:
fn main() {
let mut x = Droppable(0);
let p: *mut _ = &mut x;
std::mem::forget(x);
unsafe { *p = Droppable(1); } // prints "dropping 0"
// 1 isn’t dropped
}
Compiler suppresses drop for x
, whenever it goes out of scope with whatever value at that time. But raw ptr writes are immune to that suppression. Perhaps goes without saying but ...
Surely that *p
is undefined behavior? You're responsible for making sure the addressed memory is still alive, but here the original value has been moved into the forget
call. It may just work (hence "dropping 0"), or you may have just "dropped" and clobbered something else reusing that space on the stack.
I suppose whether it’s UB depends on the meaning of leaking (forgetting) a stack location. std::mem::forget
says:
Perhaps the last sentence there implies UB because if the memory isn’t valid, writing to it via a raw ptr (which involves a read) is UB (std::ptr::write
is the proper way to do this with respect to bypassing drop of existing value, but validity of stack slot is orthogonal to that). Namely, is the stack slot freed after forget() or is it leaked until the call frame itself is torn down? If it is freed, then the above is UB but then I’d expect this to be stated clearly somewhere (maybe it is and I either missed it or looking for something more explicit). If it’s not freed, then causing a drop regardless of previous forget() seems valid in so far as there’s still a valid value in there (that we forgot) and dropping a subsequent value is perhaps surprising but also valid from a memory safety perspective.
So maybe it’s just me but I can’t say anything is “surely” in this case.
Maybe @RalfJung can clarify.
The local semantics of forget
aren't special -- it's just a function call consuming your value as an argument. Your value moves into a new calling frame and never returns. Whether the local stack slot is actually freed or not is an implementation detail, but either way I'd still call it UB to access that memory afterward.
The implementation of forget
stuffs the value into a ManuallyDrop
, which is a lang item, so that's where the special treatment occurs.
That's a very good question! I'd say this is UB. We emit StorageDead
annotations telling LLVM about the liveness range of local variables, and using the storage after a StorageDead
is UB. I do not know if we emit StorageDead
in this particular case or not, but I'd say we'd probably want to.
(It seems we don't, Miri doesn't complain here but it would detect use-after-StorageDead
.)
Cc @nikomatsakis @eddyb with whom I have discussed such questions before.