I have read a long discussion about unsafe fields and I find it poorly motivated for private fields. I came here to express why.
I think the feature makes sense for public fields.
The main motivation in the RFC is:
Consequently, Rust programmers will be able to use the
unsafekeyword to denote when a named field carries a library safety invariant
And then it provides examples of private fields with "library safety invariants".
But what is a "library safety invariant"? Is it different from just any other "library invariant"?
Suppose I write a non-zero type:
pub struct NonZero {
/// This is never zero.
val: u32
}
impl NonZero {
pub fn new(val: u32) -> Self {
assert_ne!(val, 0);
Self { val }
}
pub fn get(self) -> u32 {
self.val
}
}
So val being non-zero is an invariant maintained by my module.
Is val being non-zero a "library safety invariant"? Should the field be marked unsafe? Presumably not because I don't have any unsafe code in my library that relies on it.
Let's say I add such code:
impl NonZero {
pub fn crazy(self) -> u32 {
// Safety: self.val is never 0
unsafe { 100u32.checked_div(self.val).unwrap_unchecked() }
}
}
According to the RFC, val should now become an unsafe field because mistakenly setting it to 0 would cause undefined behavior, i.e. a memory safety issue.
But this is inconsistent!
Suppose I don't add crazy, and thus keep val a "safe" field.
A user of my module can do the same exact thing that I did:
fn user_crazy(x: NonZero) -> u32 {
// Safety: x is never 0
unsafe { 100u32.checked_div(x.get()).unwrap_unchecked() }
}
If any user of my module does that, mistakenly setting val to 0 still causes undefined behavior, i.e. a memory safety issue.
So as the author of my module, I can't know which of my invariants are important for memory safety. To be consistent, I would need to make all structs with any invariants have unsafe fields. But that's obviously ridiculous -- so many structs maintain some sort of invariants.
I think this feature tries to do something impossible. It tries to use unsafe to indicate places in code where bugs might cause UB. But by design, that's basically impossible. unsafe code often relies on correctness of surrounding safe code.
The only thing that can reliably protect invariants is privacy boundaries. And thus it seems to me the way invariants can interact with unsafe is to have unsafe mark APIs with requirements across privacy boundaries.
So an unsafe field might make sense when a field is public. It doesn't really make sense for private fields.
Later in the RFC it explicitly says that correctness invariants are not considered safety invariants:
The
unsafemodifier should only be used on fields with safety invariants, not merely correctness invariants.
But again, given that some code may rely on correctness of your module for safety, this distinction doesn't make sense to me.