--- title: "Design meeting 2025-09-17: Unsafe fields" tags: ["T-lang", "design-meeting", "minutes"] date: 2025-09-17 discussion: https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202025-09-17.3A.20Unsafe.20fields/ url: https://hackmd.io/i5172RUiTkuBfDzvvOWoRQ --- - Feature Name: `unsafe_fields` - Start Date: 2023-07-13 - RFC PR: [rust-lang/rfcs#3458](https://github.com/rust-lang/rfcs/pull/3458) - Rust Issue: [rust-lang/rust#132922](https://github.com/rust-lang/rust/issues/132922) # Summary This RFC proposes extending Rust's tooling support for safety hygiene to named fields that carry library safety invariants. Consequently, Rust programmers will be able to use the `unsafe` keyword to denote when a named field carries a library safety invariant; e.g.: ```rust struct UnalignedRef<'a, T> { /// # Safety /// /// `ptr` is a shared reference to a valid-but-unaligned instance of `T`. unsafe ptr: *const T, _lifetime: PhantomData<&'a T>, } ``` Rust will enforce that potentially-invalidating uses of such fields only occur in the context of an `unsafe` block, and Clippy's [`missing_safety_doc`] lint will check that such fields have accompanying safety documentation. [`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc # Motivation Safety hygiene is the practice of denoting and documenting where memory safety obligations arise and where they are discharged. Rust provides some tooling support for this practice. For example, if a function has safety obligations that must be discharged by its callers, that function *should* be marked `unsafe` and documentation about its invariants *should* be provided (this is optionally enforced by Clippy via the [`missing_safety_doc`] lint). Consumers, then, *must* use the `unsafe` keyword to call it (this is enforced by rustc), and *should* explain why its safety obligations are discharged (again, optionally enforced by Clippy). Functions are often marked `unsafe` because they concern the safety invariants of fields. For example, [`Vec::set_len`] is `unsafe`, because it directly manipulates its `Vec`'s length field, which carries the invariants that it is less than the capacity of the `Vec` and that all elements in the `Vec<T>` between 0 and `len` are valid `T`. It is critical that these invariants are upheld; if they are violated, invoking most of `Vec`'s other methods will induce undefined behavior. [`Vec::set_len`]: https://doc.rust-lang.org/std/vec/struct.Vec.html#method.set_len To help ensure such invariants are upheld, programmers may apply safety hygiene techniques to fields, denoting when they carry invariants and documenting why their uses satisfy their invariants. For example, the `zerocopy` crate maintains the policy that fields with safety invariants have `# Safety` documentation, and that uses of those fields occur in the lexical context of an `unsafe` block with a suitable `// SAFETY` comment. Unfortunately, Rust does not yet provide tooling support for field safety hygiene. Since the `unsafe` keyword cannot be applied to field definitions, Rust cannot enforce that potentially-invalidating uses of fields occur in the context of `unsafe` blocks, and Clippy cannot enforce that safety comments are present either at definition or use sites. This RFC is motivated by the benefits of closing this tooling gap. ### Benefit: Improving Field Safety Hygiene The absence of tooling support for field safety hygiene makes its practice entirely a matter of programmer discipline, and, consequently, rare in the Rust ecosystem. Field safety invariants within the standard library are sparingly and inconsistently documented; for example, at the time of writing, `Vec`'s capacity invariant is internally documented, but its length invariant is not. The practice of using `unsafe` blocks to denote dangerous uses of fields with safety invariants is exceedingly rare, since Rust actively lints against the practice with the `unused_unsafe` lint. Alternatively, Rust's visibility mechanisms can be (ab)used to help enforce that dangerous uses occur in `unsafe` blocks, by wrapping type definitions in an enclosing `def` module that mediates construction and access through `unsafe` functions; e.g.: ```rust /// Used to mediate access to `UnalignedRef`'s conceptually-unsafe fields. /// /// No additional items should be placed in this module. Impl's outside of this module should /// construct and destruct `UnalignedRef` solely through `from_raw` and `into_raw`. mod def { pub struct UnalignedRef<'a, T> { /// # Safety /// /// `ptr` is a shared reference to a valid-but-unaligned instance of `T`. pub(self) unsafe ptr: *const T, pub(self) _lifetime: PhantomData<&'a T>, } impl<'a, T> UnalignedRef<'a, T> { /// # Safety /// /// `ptr` is a shared reference to a valid-but-unaligned instance of `T`. pub(super) unsafe fn from_raw(ptr: *const T) -> Self { Self { ptr, _lifetime: PhantomData } } pub(super) fn into_raw(self) -> *const T { self.ptr } } } pub use def::UnalignedRef; ``` This technique poses significant linguistic friction and may be untenable when split borrows are required. Consequently, this approach is uncommon in the Rust ecosystem. We hope that tooling that supports and rewards good field safety hygiene will make the practice more common in the Rust ecosystem. ### Benefit: Improving Function Safety Hygiene Rust's safety tooling ensures that `unsafe` operations may only occur in the lexical context of an `unsafe` block or `unsafe` function. When the safety obligations of an operation cannot be discharged entirely prior to entering the `unsafe` block, the surrounding function must, itself, be `unsafe`. This tooling cue nudges programmers towards good function safety hygiene. The absence of tooling for field safety hygiene undermines this cue. The [`Vec::set_len`] method *must* be marked `unsafe` because it delegates the responsibility of maintaining `Vec`'s safety invariants to its callers. However, the implementation of [`Vec::set_len`] does not contain any explicitly `unsafe` operations. Consequently, there is no tooling cue that suggests this function should be unsafe — doing so is entirely a matter of programmer discipline. Providing tooling support for field safety hygiene will close this gap in the tooling for function safety hygiene. ### Benefit: Making Unsafe Rust Easier to Audit As a consequence of improving function and field safety hygiene, the process of auditing internally `unsafe` abstractions will be made easier in at least two ways. First, as previously discussed, we anticipate that tooling support for field safety hygiene will encourage programmers to document when their fields carry safety invariants. Second, we anticipate that good field safety hygiene will narrow the scope of safety audits. Presently, to evaluate the soundness of an `unsafe` block, it is not enough for reviewers to *only* examine `unsafe` code; the invariants upon which `unsafe` code depends may also be violated in safe code. If `unsafe` code depends upon field safety invariants, those invariants may presently be violated in any safe (or unsafe) context in which those fields are visible. So long as Rust permits safety invariants to be violated at-a-distance in safe code, audits of unsafe code must necessarily consider distant safe code. (See [*The Scope of Unsafe*].) [*The Scope of Unsafe*]: https://www.ralfj.de/blog/2016/01/09/the-scope-of-unsafe.html For crates that practice good safety hygiene, reviewers will mostly be able to limit their review of distant routines to only `unsafe` code. # Guide-level explanation A safety invariant is any boolean statement about the computer at a time *t*, which should remain true or else undefined behavior may arise. Language safety invariants are imposed by the Rust itself and must never be violated; e.g., a `NonZeroU8` must *never* be 0. Library safety invariants, by contrast, are imposed by an API. For example, `str` encapsulates valid UTF-8 bytes, and much of its API assumes this to be true. This invariant may be temporarily violated, so long as no code that assumes this safety invariant holds is invoked. Safety hygiene is the practice of denoting and documenting where memory safety obligations arise and where they are discharged. To denote that a field carries a library safety invariant, use the `unsafe` keyword in its declaration and document its invariant; e.g.: ```rust pub struct UnalignedRef<'a, T> { /// # Safety /// /// `ptr` is a shared reference to a valid-but-unaligned instance of `T`. unsafe ptr: *const T, _lifetime: PhantomData<&'a T>, } ``` You should use the `unsafe` keyword on any field that carries a library safety invariant which differs from the invariant provided by its type. The `unsafe` field modifier is only applicable to named fields. You should avoid attaching library safety invariants to unnamed fields. Rust provides tooling to help you maintain good field safety hygiene. Clippy's [`missing_safety_doc`] lint checks that `unsafe` fields have accompanying safety documentation. The Rust compiler enforces that uses of `unsafe` fields that could violate its invariant — i.e., initializations, writes, references, and copies — must occur within the context of an `unsafe` block. For example, compiling this program: ```rust #![forbid(unsafe_op_in_unsafe_fn)] pub struct Alignment { /// SAFETY: `pow` must be between 0 and 29 (inclusive). pub unsafe pow: u8, } impl Alignment { pub fn new(pow: u8) -> Option<Self> { if pow > 29 { return None; } Some(Self { pow }) } pub fn as_log(self) -> u8 { self.pow } /// # Safety /// /// The caller promises to not write a value greater than 29 into the returned reference. pub unsafe fn as_mut_log(&mut self) -> &mut u8 { &mut self.pow } } ``` ...emits the errors: ``` error[E0133]: initializing type with an unsafe field is unsafe and requires unsafe block --> src/lib.rs:14:14 | 14 | Some(Self { pow }) | ^^^^^^^^^^^^ initialization of struct with unsafe field | = note: unsafe fields may carry library invariants error[E0133]: use of unsafe field is unsafe and requires unsafe block --> src/lib.rs:18:9 | 18 | self.pow | ^^^^^^^^ use of unsafe field | = note: unsafe fields may carry library invariants error[E0133]: use of unsafe field is unsafe and requires unsafe block --> src/lib.rs:25:14 | 25 | &mut self.pow | ^^^^^^^^ use of unsafe field | = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html> = note: unsafe fields may carry library invariants note: an unsafe function restricts its caller, but its body is safe by default --> src/lib.rs:24:5 | 24 | pub unsafe fn as_mut_lug(&mut self) -> &mut u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: the lint level is defined here --> src/lib.rs:1:38 | 1 | #![forbid(unsafe_op_in_unsafe_fn)] | ^^^^^^^^^^^^^^^^^^^^^^ For more information about this error, try `rustc --explain E0133`. ``` ...which may be resolved by wrapping the use-sites in `unsafe { ... }` blocks; e.g.: ```diff #![forbid(unsafe_op_in_unsafe_fn)] pub struct Alignment { /// SAFETY: `pow` must be between 0 and 29 (inclusive). pub unsafe pow: u8, } impl Alignment { pub fn new(pow: u8) -> Option<Self> { if pow > 29 { return None; } - Some(Self { pow }) + // SAFETY: We have ensured that `pow <= 29`. + Some(unsafe { Self { pow } }) } pub fn as_log(self) -> u8 { - self.pow + // SAFETY: Copying `pow` does not violate its invariant. + unsafe { self.pow } } /// # Safety /// /// The caller promises to not write a value greater than 29 into the returned reference. pub unsafe fn as_mut_lug(&mut self) -> &mut u8 { - &mut self.pow + // SAFETY: The caller promises not to violate `pow`'s invariant. + unsafe { &mut self.pow } } } ``` You may use `unsafe` to denote that a type relaxes its type's library safety invariant; e.g.: ```rust struct MaybeInvalidStr { /// SAFETY: `maybe_invalid` may not contain valid UTF-8. Nonetheless, it MUST always contain /// initialized bytes (per language safety invariant on `str`). unsafe maybe_invalid: str } ``` ...but you *must* ensure that the field is soundly droppable before it is dropped. A `str` is bound by the library safety invariant that it contains valid UTF-8, but because it is trivially destructible, no special action needs to be taken to ensure it is in a safe-to-drop state. By contrast, `Box` has a non-trivial destructor which requires that its referent has the same size and alignment that the referent was allocated with. Adding the `unsafe` modifier to a `Box` field that violates this invariant; e.g.: ```rust struct BoxedErased { /// SAFETY: `data`'s logical type has `type_id`. unsafe data: Box<[MaybeUninit<u8>]>, /// SAFETY: See [`BoxErased::data`]. unsafe type_id: TypeId, } impl BoxedErased { fn new<T: 'static>(src: Box<T>) -> Self { let data = …; // cast `Box<T>` to `Box<[MaybeUninit<u8>]>` let type_id = TypeId::of::<T>; // SAFETY: … unsafe { BoxedErased { data, type_id, } } } } ``` ...does not ensure that using `BoxedErased` or its `data` field in safe contexts cannot lead to undefined behavior: namely, if `BoxErased` or its `data` field is dropped, its destructor may induce UB. In such situations, you may avert the potential for undefined behavior by wrapping the problematic field in `ManuallyDrop`; e.g.: ```diff struct BoxedErased { /// SAFETY: `data`'s logical type has `type_id`. - unsafe data: Box<[MaybeUninit<u8>]>, /// SAFETY: See [`BoxErased::data`]. + unsafe data: ManuallyDrop<Box<[MaybeUninit<u8>]>>, unsafe type_id: TypeId, } ``` ## When *Not* To Use Unsafe Fields ### Relaxing a Language Invariant The `unsafe` modifier is appropriate only for denoting *library* safety invariants. It has no impact on *language* safety invariants, which must *never* be violated. This, for example, is an unsound API: ```rust struct Zeroed<T> { // SAFETY: The value of `zeroed` consists only of bytes initialized to `0`. unsafe zeroed: T, } impl<T> Zeroed<T> { pub fn zeroed() -> Self { unsafe { Self { zeroed: core::mem::zeroed() }} } } ``` ...because `Zeroed::<NonZeroU8>::zeroed()` induces undefined behavior. ### Denoting a Correctness Invariant A library *correctness* invariant is an invariant imposed by an API whose violation must not result in undefined behavior. In the below example, unsafe code may rely upon `alignment_pow`s invariant, but not `size`'s invariant: ```rust struct Layout { /// The size of a type. /// /// # Invariants /// /// For well-formed layouts, this value is less than `isize::MAX` and is a multiple of the alignment. /// To accomodate incomplete layouts (i.e., those missing trailing padding), this is not a safety invariant. pub size: usize, /// The log₂(alignment) of a type. /// /// # Safety /// /// `alignment_pow` must be between 0 and 29. pub unsafe alignment_pow: u8, } ``` The `unsafe` modifier should only be used on fields with *safety* invariants, not merely correctness invariants. We might also imagine a variant of the above example where `alignment_pow`, like `size`, doesn't carry a safety invariant. Ultimately, whether or not it makes sense for a field to be `unsafe` is a function of programmer preference and API requirements. ## Complete Example The below example demonstrates how field safety support can be applied to build a practical abstraction with small safety boundaries ([playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2024&gist=e8aa2af933f5bf4892d1be951062538d)): ```rust #![deny( unfulfilled_lint_expectations, clippy::missing_safety_doc, clippy::undocumented_unsafe_blocks, )] use std::{ cell::UnsafeCell, ops::{Deref, DerefMut}, sync::Arc, }; /// An `Arc` that provides exclusive access to its referent. /// /// A `UniqueArc` may have any number of `KeepAlive` handles which ensure that /// the inner value is not dropped. These handles only control dropping, and do /// not provide read or write access to the value. pub struct UniqueArc<T: 'static> { /// # Safety /// /// While this `UniqueArc` exists, the value pointed by this `arc` may not /// be accessed (read or written) other than via this `UniqueArc`. unsafe arc: Arc<UnsafeCell<T>>, } /// Keeps the parent [`UniqueArc`] alive without providing read or write access /// to its value. pub struct KeepAlive<T> { /// # Safety /// /// `T` may not be accessed (read or written) via this `Arc`. #[expect(unused)] unsafe arc: Arc<UnsafeCell<T>>, } impl<T> UniqueArc<T> { /// Constructs a new `UniqueArc` from a value. pub fn new(val: T) -> Self { let arc = Arc::new(UnsafeCell::new(val)); // SAFETY: Since we have just created `arc` and have neither cloned it // nor leaked a reference to it, we can be sure `T` cannot be read or // accessed other than via this particular `arc`. unsafe { Self { arc } } } /// Releases ownership of the enclosed value. /// /// Returns `None` if any `KeepAlive`s were created but not destroyed. pub fn into_inner(self) -> Option<T> { // SAFETY: Moving `arc` out of `Self` releases it from its safety // invariant. let arc = unsafe { self.arc }; Arc::into_inner(arc).map(UnsafeCell::into_inner) } /// Produces a `KeepAlive` handle, which defers the destruction /// of the enclosed value. pub fn keep_alive(&self) -> KeepAlive<T> { // SAFETY: By invariant on `KeepAlive::arc`, this clone will never be // used for accessing `T`, as required by `UniqueArc::arc`. The one // exception is that, if a `KeepAlive` is the last reference to be // dropped, then it will drop the inner `T`. However, if this happens, // it means that the `UniqueArc` has already been dropped, and so its // invariant will not be violated. unsafe { KeepAlive { arc: self.arc.clone(), } } } } impl<T> Deref for UniqueArc<T> { type Target = T; fn deref(&self) -> &T { // SAFETY: We do not create any other owning references to `arc` - we // only dereference it below, but do not clone it. let arc = unsafe { &self.arc }; let ptr = UnsafeCell::get(arc); // SAFETY: We satisfy all requirements for pointer-to-reference // conversions [1]: // - By invariant on `&UnsafeCell<T>`, `ptr` is well-aligned, non-null, // dereferenceable, and points to a valid `T`. // - By invariant on `Self::arc`, no other `Arc` references exist to // this value which will be used for reading or writing. Thus, we // satisfy the aliasing invariant of `&` references. // // [1] https://doc.rust-lang.org/1.85.0/std/ptr/index.html#pointer-to-reference-conversion unsafe { &*ptr } } } impl<T> DerefMut for UniqueArc<T> { fn deref_mut(&mut self) -> &mut T { // SAFETY: We do not create any other owning references to `arc` - we // only dereference it below, but do not clone it. let arc = unsafe { &mut self.arc }; let val = UnsafeCell::get(arc); // SAFETY: We satisfy all requirements for pointer-to-reference // conversions [1]: // - By invariant on `&mut UnsafeCell<T>`, `ptr` is well-aligned, // non-null, dereferenceable, and points to a valid `T`. // - By invariant on `Self::arc`, no other `Arc` references exist to // this value which will be used for reading or writing. Thus, we // satisfy the aliasing invariant of `&mut` references. // // [1] https://doc.rust-lang.org/1.85.0/std/ptr/index.html#pointer-to-reference-conversion unsafe { &mut *val } } } ``` # Reference-level explanation ## Syntax The [`StructField` syntax][struct syntax], used for the named fields of structs, enums, and unions, shall be updated to accommodate an optional `unsafe` keyword just before the field `IDENTIFIER`: ```diff StructField : OuterAttribute* Visibility? + unsafe? IDENTIFIER : Type ``` [struct syntax]: https://doc.rust-lang.org/stable/reference/items/structs.html#structs The use of unsafe fields on unions shall remain forbidden while the [impact of this feature on unions](#safe-unions) is decided. # Rationale and Alternatives The design of this proposal is primarily guided by three tenets: 1. [**Unsafe Fields Denote Safety Invariants**](#tenet-unsafe-fields-denote-safety-invariants) A field *should* be marked `unsafe` if it carries arbitrary library safety invariants with respect to its enclosing type. 2. [**Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe) Uses of `unsafe` fields which could violate their invariants *must* occur in the scope of an `unsafe` block. 3. [**Safe Usage is Usually Safe**](#tenet-safe-usage-is-usually-safe) Uses of `unsafe` fields which cannot violate their invariants *should not* require an unsafe block. This RFC prioritizes the first two tenets before the third. We believe that the benefits doing so — broader utility, more consistent tooling, and a simplified safety hygiene story — outweigh its cost, [alarm fatigue](#alarm-fatigue). The third tenet implores us to weigh this cost. ## Tenet: Unsafe Fields Denote Safety Invariants > A field *should* be marked `unsafe` if it carries library safety invariants. We adopt this tenet because it is consistent with the purpose of the `unsafe` keyword in other declaration positions, where it signals to consumers of the `unsafe` item that their use is conditional on upholding safety invariants; for example: - An `unsafe` trait denotes that it carries safety invariants which must be upheld by implementors. - An `unsafe` function denotes that it carries safety invariants which must be upheld by callers. ## Tenet: Unsafe Usage is Always Unsafe > Uses of `unsafe` fields which could violate their invariants *must* occur in the scope of an > `unsafe` block. We adopt this tenet because it is consistent with the requirements imposed by the `unsafe` keyword imposes when applied to other declarations; for example: - An `unsafe` trait may only be implemented with an `unsafe impl`. - An `unsafe` function is only callable in the scope of an `unsafe` block. ## Tenet: Safe Usage is Usually Safe > Uses of `unsafe` fields which cannot violate their invariants *should not* require an unsafe block. Good safety hygiene is a social contract and adherence to that contract will depend on the user experience of practicing it. We adopt this tenet as a forcing function between designs that satisfy our first two tenets. All else being equal, we give priority to designs that minimize the needless use of `unsafe`. ## Alternatives These tenets effectively constrain the design space of tooling for field safety hygiene; the alternatives we have considered conflict with one or more of these tenets. ### Unsafe Variants We propose that the `unsafe` keyword be applicable on a per-field basis. Alternatively, we can imagine it being applied on a per-constructor basis; e.g.: ```rust // SAFETY: ... unsafe struct Example { foo: X, bar: Y, baz: Z, } enum Example { Foo, // SAFETY: ... unsafe Bar(baz) } ``` For structs and enum variants with multiple unsafe fields, this alternative has a syntactic advantage: the `unsafe` keyword need only be typed once per enum variant or struct with safety invariant. However, in structs and enum variants with mixed safe and unsafe fields, this alternative denies programmers a mechanism for distinguishing between conceptually safe and unsafe fields. Consequently, any safety tooling built upon this mechanism must presume that *all* fields of such variants are conceptually unsafe, requiring the programmer to use `unsafe` even for the consumption of 'safe' fields. This violates [*Tenet: Safe Usage is Usually Safe*](#tenet-safe-usage-is-usually-safe). ### Field Moving is Safe We propose that all uses of `unsafe` fields require `unsafe`, including reading. Alternatively, we might consider making reads safe. However, a field may carry an invariant that would be violated by a read. In the [*Complete Example*](#complete-example), `KeepAlive<T>::arc` is marked `unsafe` because it carries such an invariant: ```rust /// Keeps the parent [`UniqueArc`] alive without providing read or write access /// to its value. pub struct KeepAlive<T> { /// # Safety /// /// `T` may not be accessed (read or written) via this `Arc`. unsafe arc: Arc<UnsafeCell<T>>, } ``` Allowing `arc` to be safely moved out of `KeepAlive<T>` would create the false impression that it is safe to use `arc` — it is not. By requiring `unsafe` to read `arc`, Rust's safety tooling ensures a narrow safety boundary: the user is forced to justify their actions when accessing `arc` (which documents its safety conditions as they relate to `KeepAlive`), rather than in downstream interactions with `UnsafeCell<T>` (whose methods necessarily provide only general guidance). Consequently, we require that moving unsafe fields out of their enclosing type requires `unsafe`. ### Field Copying is Safe We propose that all uses of unsafe fields require `unsafe`, including copying. Alternatively, we might consider making field copies safe. However, a field may carry an invariant that could be violated as consequence a copy. For example, consider a field of type `&'static RefCell<T>` that imposes an invariant on the value of `T`. In this alternative proposal, such a field could be safely copiable out of its enclosing type, then safely mutated via the API of `RefCell`. Consequently, we require that copying unsafe fields out of their enclosing type requires `unsafe`. ### Copy Is Safe To Implement We propose that `Copy` is conditionally unsafe to implement; i.e., that the `unsafe` modifier is required to implement `Copy` for types that have unsafe fields. Alternatively, we can imagine permitting retaining Rust's present behavior that `Copy` is unconditionally safe to implement for all types; e.g.: ```rust struct UnalignedMut<'a, T> { /// # Safety /// /// `ptr` is an exclusive reference to a valid-but-unaligned instance of `T`. unsafe ptr: *mut T, _lifetime: PhantomData<&'a T>, } impl<'a, T> Copy for UnalignedMut<'a, T> {} impl<'a, T> Clone for UnalignedMut<'a, T> { fn clone(&self) -> Self { *self } } ``` However, the `ptr` field introduces a declaration-site safety obligation that is not discharged with `unsafe` at any use site; this violates [**Tenet: Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe). ### Non-Trivial Destructors are Prohibited If a programmer applies the `unsafe` modifier to a field with a non-trivial destructor and relaxes its invariant beyond that which is required by the field's destructor, Rust cannot prevent the unsound use of that field in safe contexts. This is, seemingly, a soft violation of [**Tenet: Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe). We resolve this by documenting that such fields are a serious violation of good safety hygiene, and accept the risk that this documentation is ignored. This risk is minimized by prevalence: we feel that relaxing a field's invariant beyond that of its destructor is a rare subset of the cases in which a field carries a relaxed variant, which itself a rare subset of the cases in which a field carries a safety invariant. Alternatively, we previously considered that this risk might be averted by requiring that `unsafe` fields have trivial destructors, à la union fields, by requiring that `unsafe` field types be either `Copy` or `ManuallyDrop`. Unfortunately, we discovered that adopting this approach would contradict our design tenets and place library authors in an impossible dilemma. To illustrate, let's say a library author presently provides an API this this shape: ```rust pub struct SafeAbstraction { pub safe_field: NotCopy, // SAFETY: [some additive invariant] unsafe_field: Box<NotCopy>, } ``` ...and a downstream user presently consumes this API like so: ```rust let val = SafeAbstraction::default(); let SafeAbstraction { safe_field, .. } = val; ``` Then, `unsafe` fields are stabilized and the library author attempts to refactor their crate to use them. They mark `unsafe_field` as `unsafe` and — dutifully following the advice of a rustc diagnostic — wrap the field in `ManuallyDrop`: ```rust pub struct SafeAbstraction { pub safe_field: NotCopy, // SAFETY: [some additive invariant] unsafe unsafe_field: ManuallyDrop<Box<NotCopy>>, } ``` But, to avoid a memory leak, they must also now provide a `Drop` impl; e.g.: ```rust impl Drop for SafeAbstraction { fn drop(&mut self) { // SAFETY: `unsafe_field` is in a library-valid // state for its type. unsafe { ManuallyDrop::drop(&mut self.unsafe_field) } } } ``` This is a SemVer-breaking change. If the library author goes though with this, the aforementioned downstream code will no longer compile. In this scenario, the library author cannot use `unsafe` to denote that this field carries a safety invariant; this is *both* a hard violation of [**Tenet: Unsafe Fields Denote Safety Invariants**](#tenet-unsafe-fields-denote-safety-invariants), and (in requiring trivially `unsafe` drop glue), a violation of [**Tenet: Safe Usage is Usually Safe**](#tenet-safe-usage-is-usually-safe). ### Unsafe Wrapper Type This RFC proposes extending the Rust language with first-class support for field (un)safety. Alternatively, we could attempt to achieve the same effects by leveraging Rust's existing visibility and safety affordances. At first blush, this seems plausible; it's trivial to define a wrapper that only provides unsafe initialization and access to its value: ```rust #[repr(transparent)] pub struct Unsafe<T: ?Sized>(T); impl<T: ?Sized> Unsafe<T> { pub fn new(val: T) -> Self where T: Sized { Self(val) } pub unsafe fn as_ref(&self) -> &T { &self.0 } pub unsafe fn as_mut(&mut self) -> &mut T { &mut self.0 } pub unsafe fn into_inner(self) -> T where T: Sized { self.0 } } ``` However, this falls short of the assurances provided by first-class support for field safety. The safety conditions of its accessors inherit the safety conditions of the field that the `Unsafe` was read or referenced from. Consequently, what safety proofs one must write when using such a wrapper depend on the dataflow of the program. And worse, certain dangerous flows do not require `unsafe` at all. For instance, unsafe fields of the same type can be laundered between fields with different invariants; safe code could exchange `Even` and `Odd`s' `val`s: ```rust struct Even { val: Unsafe<usize>, } struct Odd { val: Unsafe<usize>, } ``` We can plug this particular hole by adding a type parameter to `Unsafe` that encodes the type of the outer datatype, `O`; e.g.: ```rust #[repr(transparent)] pub struct Unsafe<O: ?Sized, T: ?Sized>(PhantomData<O>, T); ``` However, it remains possible to exchange unsafe fields within the same type; for example, safe code can freely exchange the values of `len` and `cap` of this hypothetical vector: ```rust struct Vec<T> { alloc: Unsafe<Self, *mut T>, len: Unsafe<Self, usize>, cap: Unsafe<Self, usize>, } ``` The [`unsafe-fields`](https://crates.io/crates/unsafe-fields) crate plugs this hole by extending `Unsafe` with a const generic that holds a hash of the field name. Even so, it remains possible for safe code to exchange the same unsafe field between different instances of the same type (e.g., exchanging the `len`s of two instances of the aforementioned `Vec`). These challenges motivate first-class support for field safety tooling. ### More Syntactic Granularity This RFC proposes the rule that *a field marked `unsafe` is unsafe to use*. This rule is flexible enough to handle arbitrary field invariants, but — in some scenarios — requires that the user write trivial safety comments. For example, in some scenarios, an unsafe is trivially sound to read: ```rust struct Even { /// # Safety /// /// `val` is an even number. val: u8, } impl Into<u8> for Even { fn into(self) -> u8 { // SAFETY: Reading this `val` cannot // violate its invariant. unsafe { self.val } } } ``` In other scenarios, an unsafe field is trivially sound to `&`-reference (but not `&mut`-reference). Since it is impossible for the compiler to precisely determine the safety requirements of an unsafe field from a type-directed analysis, we must *either* choose a usage rule that fits all scenarios (i.e., the approach adopted by this RFC) *or* provide the user with a mechanism to signal their requirements to the compiler. Here, we explore this alternative. The design space of syntactic knobs is vast. For instance, we could require that the user enumerate the operations that require `unsafe`; e.g.: - `unsafe(init,&mut,&,read)` (everything is unsafe) - `unsafe(init,&mut,&)` (everything except reading unsafe) - `unsafe(init,&mut)` (everything except reading and `&`-referencing unsafe) - etc. Besides the unclear semantics of an unparameterized `unsafe()`, this design has the disadvantage that the most permissive (and thus dangerous) semantics are the cheapest to type. To mitigate this, we might instead imagine reversing the polarity of the modifier: - `safe(read)` all operations except reading are safe - `safe(read,&)` all operations except reading and `&`-referencing are safe - etc. ...but using `safe` to denote the presence of a safety invariant is probably too surprising in the context of Rust's existing safety tooling. Alternatively, if we are confident that a hierarchy of operations exists, the brevity of the API can be improved by having the presence of one modifier imply others (e.g., `unsafe(&mut)` could denote that initialization, mutation and `&mut`-referencing) are unsafe. However, this requires that the user internalize this hierarchy, or else risk selecting the wrong modifier for their invariant. Although we cannot explore the entire design space of syntactic modifiers here, we broadly feel that their additional complexity exceeds that of our proposed design. Our proposed rule that *a field marked `unsafe` is unsafe to use* is both pedagogically simple and fail safe; i.e., so long as a field is marked `unsafe`, it cannot be misused in such a way that its invariant is violated in safe code. ### Mixing Syntactic Knobs with a Wrapper Type One alternative proposed in this RFC's discussion recommends a combination of syntactic knobs and a wrapper type. In brief, a simple [`Unsafe` wrapper type](#unsafe-wrapper-type) would be provided, along with two field safety modifiers: - `unsafe` All uses except reading are `unsafe`. - `unsafe(mut)` All uses except reading and `&`-referencing are `unsafe`. Under this proposal, a programmer would use some combination of `unsafe`, `unsafe(mut)` and `Unsafe` to precisely tune Rust's safety tooling protections, depending on the hazards of their invariant. The primary advantage of this approach is that it results in comparatively fewer instances in which [the programmer must write a 'trivial' safety proof](#trivial-safety-proofs). However, it achieves this by front-loading the requirement that the programmer imagine all possible safety hazards of their field. A mistake, here, may lead to a false sense of security if Rust fails to require `unsafe` for uses that are, in fact, dangerous. By contrast, this RFC requires that programmers resolve these questions only on an as-needed basis; e.g., until you need to `&`-reference a field, you do not need to confront whether doing so is *always* a safe operation. This alternative also inherits some of the disadvantages of [`Unsafe` wrapper types](#unsafe-wrapper-type); namely that the safety proofs needed to operate on an `Unsafe` wrapper value depend on the dataflow of the program; the wrapper value must be traced to its originating field so that field's safety documentation may be examined. Comparatively, we believe that this RFC's proposal is both pedagogically simpler and less prone to misuse, and that these benefits outweigh its [drawbacks](#drawbacks). # Drawbacks ## Trivial Safety Proofs The primary drawback of this proposal is that it — in some scenarios — necessitates writing 'trivial' safety proofs. For example, merely reading `Vec`'s `len` field obviously cannot invalidate its invariant; nonetheless, this field, if marked `unsafe`, would be `unsafe` to read. An `unsafe` block and attendant `SAFETY` comment is required. In most cases, this is a one-time chore: the maintainer can define a *safe* accessor (i.e., [`Vec::len`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.len)) that encapsulates this proof. However, in cases where multiple, partial field borrows are required, such an accessor cannot be invoked. [Future language extensions that permit partial borrows may resolve this drawback.](#partial-borrows). At the extreme, a programmer frustrated with field safety tooling might opt to continue with the status quo approach for maintaining field invariants. Such rebuttals of safety tooling are not unprecedented in the Rust ecosystem. Even among prominent projects, it is not rare to find a conceptually unsafe function or impl that is not marked unsafe. The discovery of such functions by the broader Rust community has, occasionally, provoked controversy. This RFC takes care not to fuel such flames; e.g., [**Tenet: Unsafe Fields Denote Safety Invariants**](#tenet-unsafe-fields-denote-safety-invariants) admonishes that programmers *should* — but **not** *must* — denote field safety invariants with the `unsafe` keyword. It is neither a soundness nor security issue to continue to adhere to the current convention of using visibility to enforce field safety invariants. # Prior art Some items in the Rust standard library have `#[rustc_layout_scalar_valid_range_start]`, `#[rustc_layout_scalar_valid_range_end]`, or both. These items have identical behavior to that of unsafe fields described here. It is likely (though not required by this RFC) that these items will be required to use unsafe fields, which would reduce special-casing of the standard library. # Unresolved questions - If the syntax for restrictions does not change, what is the ordering of keywords on a field that is both unsafe and mut-restricted? ## Terminology This RFC defines three terms of art: *safety invariant*, *library safety invariant*, and *language safety invariant*. The meanings of these terms are not original to this RFC, and the question of which terms should be assigned to these meanings [is being hotly debated](https://github.com/rust-lang/unsafe-code-guidelines/issues/539). This RFC does not prescribe its terminology. Documentation of the unsafe fields tooling should reflect broader consensus, once that consensus is reached. # Future possibilities ## Partial Borrows The primary drawback of this proposal is that it — in some scenarios — necessitates writing 'trivial' safety proofs. For example, merely reading `Vec`'s `len` field obviously cannot invalidate its invariant; nonetheless, this field, if marked `unsafe`, would be `unsafe` to read. An `unsafe` block and attendant `SAFETY` comment is required. In most cases, this is a one-time chore: the maintainer can define a *safe* accessor (i.e., [`Vec::len`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.len)) that encapsulates this proof. However, in cases where multiple, partial field borrows are required, such an accessor cannot be invoked. Future language extensions that permit partial borrows will resolve this drawback. ## Syntactic Knobs and Wrapper Types While we are confident that this RFC has the best tradeoffs among the alternatives in the design space, it is not a one-way door. Changes to the default semantics of `unsafe` could be realized over an edition boundary. This RFC is also forwards-compatible with some future additions of some [combinations](#mixing-syntactic-knobs-with-a-wrapper-type) of [syntactic knobs](#more-syntactic-granularity) and [wrapper types](#unsafe-wrapper-type). For example, in addition to this RFC's `unsafe` modifier, additional variants in the form `unsafe(<modifiers>)` (e.g., `unsafe(mut)`) could be added to denote that some subset of uses is always safe. ## Safe Unions Today, unions provide language support for fields with subtractive *language* invariants. Unions may be safely defined, constructed and mutated — but require unsafe to read. Consequently, it is possible to place an union into a state where its fields cannot be soundly read, using only safe code; e.g. ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=1d816399559950ccae810c4a41fab4e9)): ```rust #[derive(Copy, Clone)] #[repr(u8)] enum Zero { V = 0 } #[derive(Copy, Clone)] #[repr(u8)] enum One { V = 1 } union Tricky { a: (Zero, One), b: (One, Zero), } let mut tricky = Tricky { a: (Zero::V, One::V) }; tricky.b.0 = One::V; // Now, neither `tricky.a` nor `tricky.b` are in a valid state. ``` The possibility of such unions makes it tricky to retrofit a mechanism for safe access: Because unsafe was not required to define or mutate this union, the invariant that makes reading sound is entirely implicit. Speculatively, it might be possible to make the subtractive language invariant of union fields *explicit*; e.g.: ```rust union MaybeUninit<T> { uninit: (), unsafe(invalid) value: ManuallyDrop<T>, } ``` Migrating today's implicitly-unsafe unions to tomorrow's explicitly-unsafe unions over an edition boundary would free up the syntactic space for safe unions. --- # Discussion ## Attendance - People: TC, nikomatsakis, scottmcm, eholk, Yosh, Jack Wrenn, Joshua Liebow-Feeser, Jules Bertholet ## Meeting roles - Driver: TC - Minutes: TC ## History TC: Tell us about the history here. jswrenn: Previously we had distinguished between additive and subtractive invariants. That introduced some significant SemVer issues, e.g. with destructors. After the last design meeting, RalfJ proposed that we just make `unsafe` unsafe. That triggered a rewrite resulting in what you see today. ## Goals today TC: What are you looking for out of the meeting today? jswrenn: Checkboxes. The key question is going to be this new rule, that unsafe is unsafe. ## Vibe checks ### nikomatsakis Ship it. I've always been in favor of a drop-dead-simple rule, so I'm happy we landed there. The most notable thing is the "conditionally unsafe" part of `Copy` but actually I secretly have another case where I've wanted the same thing -- for `Send`, specifically. I think these traits are all special enough they merit it. I think that this is not a 1-way door, either: if we found that it's not hitting the balance, we have editions. This particular rule is readily backwards compatible, because we could make more things *safe* without breaking anybody (even if they used public fields)... right? I guess it kind of changes the meaning .. maybe that's wrong. Never mind. I still think ship it! (Also, we could make a `unsafe(always)` or something if we really felt the need and change the *default* across editions.) ### scottmcm Generally in favor, I like the way it helps people read RFCs and makes derives break. I am interested in the right polarity and all that. ### TC TC: Need to read it more carefully, but there seems to be a lot to like here. As I do that, what I'll be trying to map the earlier ontology onto how it's addressed here. What are the cases where people should still write a custom wrapped type and use that fo a field instead versus what are the cases where this feature is a uniquely better solution? I'll be interested to hear more from Jack about his throughts here. ### eholk This looks really nice. It's a lightweight way of identifying where there are safety implications that are not obvious today. I think the idea of an `unsafe` field is something that clicks pretty quickly. ### joshlf IMO we should be careful to paint ourselves into any corners. We've historically often missed subtleties that we later discover months or years later, and so a conservative approach is warranted. It's also easier to teach "everything is `unsafe`" vs "most things are `unsafe` except for reads, but *not* including uses that invoke interior mutation." ### Jules Bertholet I think it’s possible to make a slightly more complex design (that separates additive and subtractive invariants) work while avoiding the pitfalls discussed in the last meeting. Specifically, I think wrapper types are a better fit for subtractive invariants specifically. NM: Are you saying this would be interesting work to do before accepting the RFC or after. Jules: Before. There are additive invariants and subtractive invariants. E.g., for `str`, normally it can be any valid UTF-8 string, but an additive invariant would be saying that it has to have some particular form. A subtractive invariant would remove a requirement and say, e.g., that sometimes it doesn't need to be UTF-8. As proposed in the RFC, the rules are good for additive invariants, but aren't as good for subtractive invariants. NM: I would think the RFC as designed is suitable for both. Joshua: There are instances where something might not mutate at all. If we have a `Box<_>`, being able to copy it would be really bad. We could be modeling something outside of the Rust model, e.g. things like `/proc/mem`. Joshua: Jules, could you describe the delta between what you're proposing and what's in the alternative section? Jules: In the alternative section, it mentions a wrapper type -- I completely agree with how it talks about that, that an `Unsafe<_>` wrapper type alone isn't enough to address all of the things that we need here. Similarly, unsafe copying alone isn't enough. But if we can combine them in some way, I'd suggest that's more flexible than the RFC. Joshua: What's the form of that flexibility? Jules: Let's go through some examples. scottmcm: Let's put that in a section in the document here. Jules: I wrote it as a comment in the RFC; I'll copy it in here. ## Scott's anecdote! nikomatsakis: In the RFC text it says: > For crates that practice good safety hygiene, reviewers will mostly be able to limit their review of distant routines to only unsafe code. I think Scott's anecdote of allowing in an unsoundness in libstd because they didn't realize there was a field invariant at play is very apropos. Can't remember the details though. scottmcm: I knew there was a safety invariant, and all the code I wrote was really careful to maintain it. But I forgot to think about the derives, and the derives had no way to know the safety invariant existed. NM: It'd be a nice touch to link to this PR from the RFC. scottmcm: The piece that was particularly persuasive to me was that all of the code that I wrote was correct, but there were two leftover derives that introduced the unsoundness. Joshua: Is that an argument in favor of this simpler model? scottmcm: The specific thing here was `Clone`, as I recall, and I think that'd already fail, as it'd blow up when constructing the new thing. ## Nit: wording in the guide section nikomatsakis: The RFC says > You should use the `unsafe` keyword on any field that carries a library safety invariant which differs from the invariant provided by its type. I think this woudl be clearer if it gave a "heuristic" like -- if assigning an invalid (but well typed) value to this field could cause UB, it needs to be declared `unsafe`. scottmcm: It might also be good to follow the <https://www.ralfj.de/blog/2018/08/22/two-kinds-of-invariants.html> terminology, which uses *validity* invariant for things like `NonZeroU8` that are stronger than library-level *safety* invariants. ## Big fan of addressing the general problem area scottmcm: The "shiny future" here, I think, is this: > If the body is safe, it doesn't need to be `unsafe fn` (assuming all the types are annotated correctly). That's a great explanation that's way easier than talking about tootsie pops as soon as they ask about `Vec::set_len` 🙃 NM: RIP tootsie pip :cry: ## Making the common thing the short-syntax thing scottmcm: I'm really really not a fan of forcing ```rust // SAFETY: Copying `pow` does not violate its invariant. ``` all over the place. At the very least I think `unsafe len: usize` should be the "look, only `mut` uses are unsafe" version, with `unsafe(bikeshed) len: usize` available for the more unusual cases. I think that's particularly true because you can always make the read of an inner field `unsafe` via library API things -- like `UnsafeCell` -- so the important thing that the language support needs to handle is keeping you from safely `replace`ing the whole wrapper, but that's a `mut` use. So I think there's at least one more middle-ground wrapper here where you have `unsafe foo: UnsafeToRead<Foo>` if you want the "everything unsafe" version. joshlf: IIUC there are cases where reading/copying a field can have semantic consequences, and so this is unavoidable, right? Of course, something like `unsafe(bikeshed)` is forwards-compatible with the present proposal, so we could always refine it later. joshlf: Re: `unsafe` library read APIs – that doesn't address the problem of nudging users towards the right behavior. In Fuchsia, we ran into issues like this when reviewing unsafe third-party code, where the easy thing to do is to just make fields public, and so we would run into cases where what *should* have been a straightforward safety review was far harder and laborious because we had to audit *all* code in the crate instead of just `unsafe` blocks. For the cases where reading/copying is unsafe, we don't want to have the default behavior be the less careful behavior. joshlf: One of the benefits of `unsafe` fields is that they make it so that you're encouraged to think more precisely about your safety invariants. I've found that the most common failure mode for unsafe code is not having thought through just how subtle a library's safety invariants need to be. Unsafe-to-read is arguably one of the *most* subtle forms of this, and so it's IMO quite dangerous to make the easiest thing to do to just *assume* that reading is safe. --- TC: Was having a similar thought as scottmcm here about, when the assertion is about the relationship between fields, whether `unsafe` on the field best expresses that or whether moving the `unsafe` further out would make more sense. That's what we do for functions, of course -- we mark the funciton as unsafe rather than marking the parameters as unsafe. We could do that, but then we'd run into this same thing about what happens when the interaction between parameters is what's important. scottmcm, is that part of what you're getting at here? scottmcm: Yes, and... Joshua: I think we should be careful about not implying our intuitions about Rust language design from other places to this. There are a lot of nuances here. If you look inside of `zerocopy`, we do a lot of things to emulate unsafe fields. We do a lot of unsafe analysis all the time, so that's OK. The target audience here are people who do this kind of analysis more intermittently. jswrenn: To the exact case of `Deref` and `DerefMut`, the supertrait relationship got in our way there. These ontologies are tricky to figure out. jswrenn: On the question of where to put the unsafe, there have been conversations about having types that are unsafe to name, and I wanted to reserve this syntactic space for that. Also, the granularity is important for tooling. This also gives our tooling the ability to push people to write safety documentation. scottmcm: Suppose that we had the mixins so that, without extra cost, you could make a separate struct for that. Is it still true that this specific field isolation would be important? Trivially, you could always put all the unsafe fields in another struct. jswrenn: In zerocopy, we have some interesting cases. Also, when you lack the affordance, sometimes people just don't do the thing. In the current proposal, I can do type by type in the standard library and then field by field. Versus, putting it on a whole type it requires people to upgrade a whole type at once. And if it requires restructing your code through other wrapper types, that may be something that people just never do. NM: It seems common that it's not going to be every field, and even if it is, it doesn't seem such a problem to put it at the level of each field. NM: I've noticed that when we can be precise it helps people with a fuzzy intuition to be able to better lean on the compiler. scottmcm: I've noticed that sometimes people use the struct constructor rather than the initializer function just to avoid writing what they see as a trivial safety comment. scottmcm: (Overall) I think I'm happy to accept the RFC, but I think it's still likely that I wouldn't want to stabilize without `unsafe(mut)` (or whatever). Let's see how things come out in core+alloc's unsafe code to see. ## Would we do this differently if we had mix-ins? scottmcm: As I understand it, the only reason that making it be `unsafe struct` isn't sufficient is that our layout rules mean that extracting new types has space cost. If we have a way around that, then we could always split safe things out from unsafe things, and it would be not a field-level question at all. For example, is `unsafe struct Foo {}` a thing we want where it's unsafe to consturct eventhough it doesn't have any fields? ## Getters don't work without borrow splitting scottmcm: If I make a `fn foo(self) -> &Foo { unsafe { &self.foo } }`, I now am restricted on using the rest of the fields in the type until we have borrow splitting or view types or whatever we're calling that at the function boundary. joshlf: Counter-point is that this could be a case where you'd just write a direct usage wrapped in `unsafe`. Of course you could also write a macro if you really wanted this in a way that doesn't borrow the whole struct. I agree that it's a small downside, but IMO it's quite small. scottmcm: I think it's materal that you have an abstraction way to avoid the unsafe requirement. The nice thing about "not everything is unsafe" is that you can make other things unsafe with a wrapper if needed, and then the `unsafe` on the field is there to keep you from bypassing the wrapper (which is what the type author has no way to do today). ## Examples of different invariants Jules Bertholet: (https://github.com/rust-lang/rfcs/pull/3458#discussion_r2040406290) ### Example 1: `UnalignedRef` Additive invariant, only relevant for mutable reference access. Because it’s additive, moving or copying the field is always safe. ```rust pub struct UnalignedRef<'a, T> { /// # Safety /// /// `ptr` is a shared reference to a valid-but-unaligned instance of `T`. unsafe(mut) ptr: *const T, _lifetime: PhantomData<&'a T>, } // Usage: fn main() { let array: [u8; 4] = [0; 4]; let unaligned_u32: *const u32 = (&raw const array).cast(); // SAFETY: `unaligned_u32` points to 4 initialized bytes let mut unaligned_ref = unsafe { UnalignedRef { ptr: unaligned_u32, _lifetime: PhantomData, } }; let _ref = &unaligned_ref.ptr; // SAFETY: `_mut_ref` is entirely unused let _mut_ref = unsafe { &mut unaligned_ref.ptr }; let _ptr = &raw const unaligned_ref.ptr; let _mut_ptr = &raw mut unaligned_ref.ptr; let copy_out = unaligned_ref.ptr; // SAFETY: doesn't modify the value at all unsafe { unaligned_ref.ptr = copy_out; } } ``` ### Example 2: `NonZeroCell` Additive invariant, relevant for mutable and shared reference access. Because it’s additive, moving or copying the field is always safe. (Note: the distinction between examples 1 and 2 isn’t really relevant for the MVP) ```rust pub struct NonZeroCell { /// # Safety /// /// Contents never equal zero. unsafe cell: Cell<u32>, } // Usage: fn main() { // SAFETY: `cell` is not 0 let mut nonzero_cell = unsafe { NonZeroCell { cell: Cell::new(42), } }; // SAFETY: `_ref` is entirely unused let _ref = unsafe { &nonzero_cell.cell }; // SAFETY: `_mut_ref` is entirely unused let _mut_ref = unsafe { &mut nonzero_cell.cell }; let _ptr = &raw const nonzero_cell.cell; let _mut_ptr = &raw mut nonzero_cell.cell; let move_out = nonzero_cell.cell; // SAFETY: doesn't modify the value at all unsafe { nonzero_cell.cell = move_out; } } ``` ### Example 3: `MaybeInvalidString` Subtractive invariant, which means moves and copies are also unsafe. ```rust struct MaybeInvalidString { /// # Safety /// /// May contain invalid UTF-8. /// But must still point to a valid allocation /// with `length` bytes fully initialized. unsafe(mut) maybe_invalid: Unsafe<String>, } /// Returns a `String` that is invalid UTF-8. fn make_an_invalid_string() -> Unsafe<String> { let mut invalid = Unsafe::new("hello".to_owned()); // SAFETY: we turn the String into invalid UTF-8, // but it's wrapped in an `Unsafe` so this is fine unsafe { let bytes_mut = invalid.get_mut().as_mut_str().as_bytes_mut(); bytes_mut[0] = 0xFF; bytes_mut[1] = 0xFF; } invalid } fn main() { // SAFETY: `maybe_invalid` points to a valid allocation // and is fully initialized up to its length // (though it is not valid UTF-8 let mut string = unsafe { MaybeInvalidString { maybe_invalid: make_an_invalid_string(), } }; let ref_ = &string.maybe_invalid; // SAFETY: `_inner_ref` is entirely unused let _inner_ref = unsafe { ref_.get() }; // SAFETY: `mut_ref` is not used to swap out the value let mut_ref = unsafe { &mut string.maybe_invalid }; // SAFETY: `_inner_mut_ref` is entirely unused let _inner_mut_ref = unsafe { mut_ref.get_mut() }; let _ptr = &raw const string.maybe_invalid; let _mut_ptr = &raw mut string.maybe_invalid; let move_out = string.maybe_invalid; // SAFETY: doesn't modify the value at all unsafe { string.maybe_invalid = move_out; } let move_out_again = string.maybe_invalid; // SAFETY: `_inner_ref` is entirely unused let _inner_ref = unsafe { move_out_again.get() }; } ``` nikomatsakis: Wouldn't we do this?: ```rust struct MaybeInvalidString { /// # Safety /// /// May contain invalid UTF-8. /// But must still point to a valid allocation /// with `length` bytes fully initialized. unsafe maybe_invalid: String, } // if you write this, you'd need an `unsafe` keyword to access it ``` Summary of Jules' argument * if `unsafe` preventing reads is a pain for "additive" invariants (can't read) * *and* it may not be everything you need for "subtractive" invariants (making the read unsafe still allows you to move the value outside) * then maybe we should say "wrapper types are appropriate for this" ### Example 4: `UniqueArc` Additive and subtractive invariant together ```rust /// An `Arc` that provides exclusive access to its referent. /// /// A `UniqueArc` may have any number of `KeepAlive` handles which ensure that /// the inner value is not dropped. These handles only control dropping, and do /// not provide read or write access to the value. pub struct UniqueArc<T: 'static> { /// # Safety /// /// While this `UniqueArc` exists, the value pointed by this `arc` may not /// be accessed (read or written) other than via this `UniqueArc`. unsafe arc: Arc<UnsafeCell<T>>, } /// Keeps the parent [`UniqueArc`] alive without providing read or write access /// to its value. pub struct KeepAlive<T> { /// # Safety /// /// `T` may not be accessed (read or written) via this `Arc`. #[expect(unused)] arc: Unsafe<Arc<UnsafeCell<T>>>, } fn main() { // SAFETY: The `Arc` is newly created, // so there are no other clones of it around let unique_arc: UniqueArc<i32> = unsafe { UniqueArc { arc: Arc::new(UnsafeCell::new(42)), } }; let keep_alive = KeepAlive { // SAFETY: we immediately put the cloned `Arc` in a `KeepAlive`, // where it will be inaccessible arc: Unsafe::new(unsafe { unique_arc.arc.clone() }), }; let keep_alive_arc = keep_alive.arc; // By moving out of `arc`, we destroy the `UniqueArc`¸ // and are no longer bound by its uniqueness requirement let no_longer_unique_arc = unique_arc.arc; let cloned_arc = no_longer_unique_arc.clone(); drop(keep_alive_arc); } ```