There is no such thing as a safe world. The meaning of unsafe is far more subtle than that.
The only possible take-home message I would have from such an experience is that this crate seriously needs to be audited. Taking an arbitrary integer from an external source and converting it into an enum is something you do. not. do, as is handing out a *mut
to an enum. Forget the the compiler, I'd be after these guys with pitchforks!
(uh... constructively, of course!)
Checking invariants upon exit from an unsafe block is nonsense. Behavior considered undefined for unsafe code may describe circumstances that extend arbitrarily far into the past or the future, and into arbitrary safe code (or at least, arbitrary as far as any local analysis is concerned).
Here are two equally legitimate unsafe abstractions. At first glance they may seem equivalent, as both have the exact same methods and implementation. But the difference in placement of the unsafe
keyword makes them ultimately very different.
The first is not too different from a raw pointer; here, a Ptr
is constructed safely, and dereferenced unsafely. The caller of as_ref
must use an unsafe block to declare full responsibility for all Undefined Behavior concerning the interaction between as_ref
and new
:
pub struct Ptr(*const i8);
impl Ptr {
pub fn new(p: *const i8) { Ptr(p) }
/// # Unsafety
/// Behavior is undefined if the pointer given to `new` did not
/// point to an `i8`, or that pointer is not valid for the implicit
/// lifetime in this call.
pub unsafe fn as_ref(&self) -> &i8 { p.0.as_ref().unwrap() }
}
In the second, it is instead the caller of new who must assume responsibility for Undefined Behavior concerning this interaction. Clearly, one must be very careful about where this Ptr
is stored and how long it lives! Such an object clearly cannot be safely communicated beyond the crate API boundary.
But just because it's unusual does not mean it is illegitimate. Unsafety comes in all sorts of flavors.
pub struct Ptr(*const i8);
impl Ptr {
/// # Unsafety
/// Behavior is undefined if `p` does not point to an `i8`.
///
/// Further, you must guarantee that all possible future calls to
/// `as_ref()` only produce lifetimes which are valid for that `i8`,
/// or else the behavior of this method is undefined.
pub unsafe fn new(p: *const i8) -> Self { Ptr(p) }
pub fn as_ref(&self) -> &i8 { unsafe { p.0.as_ref() }.unwrap() }
}
You see, unsafe
is not a door into a whole new world, nor do you ever leave unsafety.
It's just a contract. All unsafe
says is that you accept full responsibility for your actions.
You know, the usual stuff.