Skip to content

Commit 36b3db9

Browse files
committed
RFC3458: Explore 'Wrapper Type' and 'More Syntactic Granularity' alternatives.
1 parent cf0516b commit 36b3db9

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

text/0000-unsafe-fields.md

+143
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,149 @@ Unsafe Fields Denote Safety Invariants**](#tenet-unsafe-fields-denote-safety-inv
757757
requiring trivially `unsafe` drop glue), a violation of [**Tenet: Safe Usage is Usually
758758
Safe**](#tenet-safe-usage-is-usually-safe).
759759

760+
### Unsafe Wrapper Type
761+
762+
This RFC proposes extending the Rust language with first-class support for field (un)safety.
763+
Alternatively, we could attempt to achieve the same effects by leveraging Rust's existing visibility
764+
and safety affordances. At first blush, this seems plausible; it's trivial to define a wrapper that
765+
only provides unsafe initialization and access to its value:
766+
767+
```rust
768+
#[repr(transparent)]
769+
pub struct Unsafe<T: ?Sized>(T);
770+
771+
impl<T: ?Sized> Unsafe<T> {
772+
773+
pub unsafe fn new(val: T) -> Self
774+
where
775+
T: Sized
776+
{
777+
Self(val)
778+
}
779+
780+
pub unsafe fn as_ref(&self) -> &T {
781+
&self.0
782+
}
783+
784+
pub unsafe fn as_mut(&mut self) -> &mut T {
785+
&mut self.0
786+
}
787+
788+
pub unsafe fn into_inner(self) -> T
789+
where
790+
T: Sized
791+
{
792+
self.0
793+
}
794+
}
795+
```
796+
797+
However, this falls short of the assurances provided by first-class support for field safety.
798+
`Unsafe::new` inherits the safety conditions of the field that the `Unsafe` will be assigned to, and
799+
the safety conditions of its accessors inherit the safety conditions of the field that the `Unsafe`
800+
was read or referenced from. Consequently, what safety proofs one must write when using such a
801+
wrapper are a product of the dataflow of the program.
802+
803+
And worse, certain dangerous flows do not require `unsafe` at all. For instance, unsafe fields of
804+
the same type can be laundered between fields with different invariants; safe code could exchange
805+
`Even` and `Odd`s' `val`s:
806+
807+
```rust
808+
struct Even {
809+
val: Unsafe<usize>,
810+
}
811+
812+
struct Odd {
813+
val: Unsafe<usize>,
814+
}
815+
```
816+
817+
We can plug this particular hole by adding a type parameter to `Unsafe` that encodes the type of the
818+
outer datatype, `O`; e.g.:
819+
820+
```rust
821+
#[repr(transparent)]
822+
pub struct Unsafe<O: ?Sized, T: ?Sized>(PhantomData<O>, T);
823+
```
824+
825+
However, it remains possible to exchange unsafe fields within the same type; for example, safe code
826+
can freely exchange the values of `len` and `cap` of this hypothetical vector:
827+
828+
```rust
829+
struct Vec<T> {
830+
alloc: Unsafe<Self, *mut T>,
831+
len: Unsafe<Self, usize>,
832+
cap: Unsafe<Self, usize>,
833+
}
834+
```
835+
836+
The [`unsafe-fields`](https://crates.io/crates/unsafe-fields) crate plugs this hole by extending
837+
`Unsafe` with a const generic that holds a hash of the field name. Even so, it remains possible for
838+
safe code to exchange the same unsafe field between different instances of the same type (e.g.,
839+
exchanging the `len`s of two instances of the aforementioned `Vec`).
840+
841+
These challenges motivate first-class support for field safety tooling.
842+
843+
## More Syntactic Granularity
844+
845+
This RFC proposes the rule that *a field marked `unsafe` is unsafe to use*. This rule is flexible
846+
enough to handle arbitrary field invariants, but — in some scenarios — requires that the user write
847+
trivial safety comments. For example, in some scenarios, an unsafe is trivially sound to read:
848+
849+
```rust
850+
struct Even {
851+
/// # Safety
852+
///
853+
/// `val` is an even number.
854+
val: u8,
855+
}
856+
857+
impl Into<u8> for Even {
858+
fn into(self) -> u8 {
859+
// SAFETY: Reading this `val` cannot
860+
// violate its invariant.
861+
unsafe { self.val }
862+
}
863+
}
864+
```
865+
866+
In other scenarios, an unsafe field is trivially sound to `&`-reference (but not `&mut`-reference).
867+
868+
Since it is impossible for the compiler to precisely determine the safety requirements of an unsafe
869+
field from a type-directed analysis, we must *either* choose a usage rule that fits all scenarios
870+
(i.e., the approach adopted by this RFC) *or* provide the user with a mechanism to signal their
871+
requirements to the compiler. Here, we explore this alternative.
872+
873+
The design space of syntactic knobs is vast. For instance, we could require that the user enumerate
874+
the operations that require `unsafe`; e.g.:
875+
876+
- `unsafe(init,&mut,&,read)` (everything is unsafe)
877+
- `unsafe(init,&mut,&)` (everything except reading unsafe)
878+
- `unsafe(init,&mut)` (everything except reading and `&`-referencing unsafe)
879+
- etc.
880+
881+
Besides the unclear semantics of an unparameterized `unsafe()`, this design has the disadvantage
882+
that the most permissive (and thus dangerous) semantics are the cheapest to type. To mitigate this,
883+
we might instead imagine reversing the polarity of the modifier:
884+
885+
- `safe(read)` all operations except reading are safe
886+
- `safe(read,&)` all operations except reading and `&`-referencing are safe
887+
- etc.
888+
889+
...but using `safe` to denote the presence of a safety invariant is probably too surprising in the
890+
context of Rust's existing safety tooling.
891+
892+
Alternatively, if we are confident that a hierarchy of operations exists, the brevity of the API can
893+
be improved by having the presence of one modifier imply others (e.g., `unsafe(&mut)` could denote
894+
that initialization, mutation and `&mut`-referencing) are unsafe. However, this requires that the
895+
user internalize this hierarchy, or else risk selecting the wrong modifier for their invariant.
896+
897+
Although we cannot explore the entire design space of syntactic modifiers here, we broadly feel that
898+
their additional complexity exceeds that of our proposed design. Our proposed rule that *a field
899+
marked `unsafe` is unsafe to use* is both pedagogically simple and fail safe; i.e., so long as a
900+
field is marked `unsafe`, it cannot be misused in such a way that its invariant is violated in safe
901+
code.
902+
760903
# Drawbacks
761904

762905
## Alarm Fatigue

0 commit comments

Comments
 (0)