Reputation: 2133
The Rust language disallows unsafe code from moving-out non-copy types from behind a raw pointer, reporting a compilation error for the following program:
use std::cell::UnsafeCell;
struct NonCopyType(u32);
fn main() {
let unsafe_cell = UnsafeCell::new(NonCopyType(123));
let ptr = unsafe_cell.get();
// Disallowed, but the code will never access
// the uninitialized unsafe cell after this.
let _ = unsafe { *ptr };
}
Compilation error:
error[E0507]: cannot move out of `*ptr` which is behind a raw pointer
--> src/main.rs:12:22
|
12 | let _ = unsafe { *ptr };
| ^^^^ move occurs because `*ptr` has type `NonCopyType`, which does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` due to previous error
What is the motivation of that error message? Is it because moving-out non-copy types from behind a raw pointer is error prone, even if the developer declared that he's expert enough to write unsafe code? Or is there some undefined behavior in the above program that I'm missing?
Upvotes: 6
Views: 2934
Reputation: 125835
What is the motivation of that error message?
The safety invariants that a programmer must uphold in order to apply the dereference operator to a raw pointer are only that the pointer be dereferenceable. Where additional guarantees must be upheld (e.g. never using the referenced value again), Rust requires that some method with those requirements be used instead.
In this case, as loganfsmyth commented, there is std::ptr::read
; or there's the inherent read
method on the raw pointer types themselves. Thus:
use std::cell::UnsafeCell;
struct NonCopyType(u32);
fn main() {
let unsafe_cell = UnsafeCell::new(NonCopyType(123));
let ptr = unsafe_cell.get();
let _ = unsafe { ptr.read() };
}
Is it because moving-out non-copy types from behind a raw pointer is error prone, even if the developer declared that he's expert enough to write unsafe code?
unsafe
has nothing to do with expertise, and everything to do with taking responsibility for upholding certain invariants that the compiler would otherwise check for you; being explicit about the invariants each method requires to be upheld, and that each caller is upholding, is absolutely key to getting this right.
Rust could, I suppose, have overloaded the safety requirements of the dereference operator so that programmers need uphold different invariants depending on context. But that would be a horrible footgun, and make reasoning about the code extremely painful both for the person writing it and anyone who later reads it.
Upvotes: 2