From 63dea6ba114845f89bb3d7f2c44fa000d8c26e22 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Thu, 13 Jul 2023 00:38:58 -0400 Subject: [PATCH 01/43] Add unsafe fields RFC --- text/0000-unsafe-fields.md | 137 +++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 text/0000-unsafe-fields.md diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md new file mode 100644 index 00000000000..71267d6bcad --- /dev/null +++ b/text/0000-unsafe-fields.md @@ -0,0 +1,137 @@ +- Feature Name: `unsafe_fields` +- Start Date: 2023-07-13 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary + +Fields may be declared `unsafe`. Unsafe fields may only be mutated (excluding interior mutability) +or initialized in an unsafe context. Reading the value of an unsafe field may occur in either safe +or unsafe contexts. An unsafe field may be relied upon as a safety invariant in other unsafe code. + +# Motivation + +Emphasis on safety is a key strength of Rust. A major point here is that any code path that can +result in undefined behavior must be explicitly marked with the `unsafe` keyword. However, the +current system is insufficient. While Rust provides the `unsafe` keyword at the function level, +there is currently no mechanism to mark fields as `unsafe`. + +For a real-world example, consider the `Vec` type in the standard library. It has a `len` field that +is used to store the number of elements present. Setting this field is exposed publicly in the +`Vec::set_len` method, which has safety requirements: + +- `new_len` must be less than or equal to `capacity()`. +- The elements at `old_len..new_len` must be initialized. + +This field is safe to read, but unsafe to mutate or initialize due to the invariants. These +invariants cannot be expressed in the type system, so they must be enforced manually. Failure to do +so may result in undefined behavior elsewhere in `Vec`. + +By introducing unsafe fields, Rust can improve the situation where a field that is otherwise safe is +used as a safety invariant. + +# Guide-level explanation + +Fields may be declared `unsafe`. Unsafe fields may only be initialized or accessed mutably in an +unsafe context. Reading the value of an unsafe field may occur in either safe or unsafe contexts. An +unsafe field may be relied upon as a safety invariant in other unsafe code. + +Here is an example to illustrate usage: + +```rust +struct Foo { + safe_field: u32, + /// Safety: Value must be an odd number. + unsafe unsafe_field: u32, +} + +// Unsafe field initialization requires an `unsafe` block. +// Safety: `unsafe_field` is odd. +let mut foo = unsafe { + Foo { + safe_field: 0, + unsafe_field: 1, + } +}; + +// Safe field: no unsafe block. +foo.safe_field = 1; + +// Unsafe field with mutation: unsafe block is required. +// Safety: The value is odd. +unsafe { foo.unsafe_field = 3; } + +// Unsafe field without mutation: no unsafe block. +println!("{}", foo.unsafe_field); +``` + +For a full description of where a mutable access is considered to have occurred (and why), see +[RFC 3323]. Keep in mind that due to reborrowing, a mutable access of an unsafe field is not +necessarily explicit. + +[RFC 3323]: https://rust-lang.github.io/rfcs/3323-restrictions.html#where-does-a-mutation-occur + +```rust +fn change_unsafe_field(foo: &mut Foo) { + // Safety: An odd number plus two remains an odd number. + unsafe { foo.unsafe_field += 2; } +} +``` + +# Reference-level explanation + +## Syntax + +Using the syntax from [the reference for structs][struct syntax], the change needed to support +unsafe fields is minimal. + +[struct syntax]: https://doc.rust-lang.org/stable/reference/items/structs.html#structs + +```diff +StructField : + OuterAttribute* + Visibility? ++ unsafe? + IDENTIFIER : Type + +TupleField : + OuterAttribute* + Visibility? ++ unsafe? + Type +``` + +## Behavior + +An unsafe field may only be mutated or initialized in an unsafe context. Failure to do so is a compile error. + +## "Mutable use" in the compiler + +The concept of a "mutable use" [already exists][mutating use] within the compiler. This catches all +situations that are relevant here, including `ptr::addr_of_mut!`, `&mut`, and direct assignment to a +field, while excluding interior mutability. As such, formal semantics of what constitutes a "mutable +use" are not stated here. + +[mutating use]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/visit/enum.PlaceContext.html#method.is_mutating_use + +# Drawbacks + +- Additional syntax for macros to handle +- More syntax to learn + +# 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? +- Are there any interactions or edge cases with other language features that need to be considered? + +# Future possibilities + +?? From 75528c09c9e5c89b9f02dd70a9db526793efd5ab Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 5 Jan 2025 12:30:43 -0500 Subject: [PATCH 02/43] RFC3458: Update Summary The revised summary contextualizes the change within Rust's existing safety hygiene tooling (both provided by the language, and by Clippy), and is less prescriptive about what constitutes a problematic use. --- text/0000-unsafe-fields.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 71267d6bcad..b4898caa29d 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -5,9 +5,25 @@ # Summary -Fields may be declared `unsafe`. Unsafe fields may only be mutated (excluding interior mutability) -or initialized in an unsafe context. Reading the value of an unsafe field may occur in either safe -or unsafe contexts. An unsafe field may be relied upon as a safety invariant in other unsafe code. +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 `unsafe` fields only occur in the context +of an `unsafe` block, and Clippy's [`missing_safety_doc`] lint will check that `unsafe` fields have +accompanying safety documentation. + +[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc # Motivation From deafcc603ba03bedcfd99bccba00c9c78f8f0f36 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 5 Jan 2025 14:12:34 -0500 Subject: [PATCH 03/43] RFC3458: Update Motivation The updated Motivation contextualizes the proposal in the practice of "safety hygiene", its tooling support, and lack thereof. We foresee three benefits of closing the tooling gap: improved field safety hygiene, improved function safety hygiene, and making unsafe code broadly easier to audit. --- text/0000-unsafe-fields.md | 124 ++++++++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 14 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index b4898caa29d..3ab8c07c07e 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -27,24 +27,120 @@ accompanying safety documentation. # Motivation -Emphasis on safety is a key strength of Rust. A major point here is that any code path that can -result in undefined behavior must be explicitly marked with the `unsafe` keyword. However, the -current system is insufficient. While Rust provides the `unsafe` keyword at the function level, -there is currently no mechanism to mark fields as `unsafe`. +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` 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.: -For a real-world example, consider the `Vec` type in the standard library. It has a `len` field that -is used to store the number of elements present. Setting this field is exposed publicly in the -`Vec::set_len` method, which has safety requirements: +```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. -- `new_len` must be less than or equal to `capacity()`. -- The elements at `old_len..new_len` must be initialized. +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*].) -This field is safe to read, but unsafe to mutate or initialize due to the invariants. These -invariants cannot be expressed in the type system, so they must be enforced manually. Failure to do -so may result in undefined behavior elsewhere in `Vec`. +[*The Scope of Unsafe*]: https://www.ralfj.de/blog/2016/01/09/the-scope-of-unsafe.html -By introducing unsafe fields, Rust can improve the situation where a field that is otherwise safe is -used as a safety invariant. +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 From 046969a63e831ad72a15439a2db795fb246be0d0 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 5 Jan 2025 16:10:38 -0500 Subject: [PATCH 04/43] RFC3458: Introduce 'Rationale and Alternatives' Adds an initial 'Rationale and Alternatives'. There's more to say here, but I need to update the Explanation sections first to keep the RFC mostly self-consistent. --- text/0000-unsafe-fields.md | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 3ab8c07c07e..88d775a7c04 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -231,6 +231,88 @@ use" are not stated here. - Additional syntax for macros to handle - More syntax to learn +# 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. + +## Tenet: Unsafe Fields Denote Safety Invariants + +> A field *should* be marked `unsafe` if it carries library safety invariants with respect to its +> enclosing type. + +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). + # Prior art Some items in the Rust standard library have `#[rustc_layout_scalar_valid_range_start]`, From a2f5d94c283238bbb13f54a603b00ce67e53bb1c Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Mon, 6 Jan 2025 12:47:22 -0500 Subject: [PATCH 05/43] RFC3458: Update Guide-Level Explanation Revises the guide-level explanation for examples of when `unsafe` should be applied to fields, when it shouldn't be applied to fields, and an example of the error reported when an unsafe field is used in a safe context. --- text/0000-unsafe-fields.md | 178 +++++++++++++++++++++++++++++++------ 1 file changed, 149 insertions(+), 29 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 88d775a7c04..8dceb384cd0 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -144,52 +144,172 @@ of distant routines to only `unsafe` code. # Guide-level explanation -Fields may be declared `unsafe`. Unsafe fields may only be initialized or accessed mutably in an -unsafe context. Reading the value of an unsafe field may occur in either safe or unsafe contexts. An -unsafe field may be relied upon as a safety invariant in other unsafe code. +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 language +itself and must never be violated; e.g., a `NonZeroU8` must *never* be 0. -Here is an example to illustrate usage: +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 -struct Foo { - safe_field: u32, - /// Safety: Value must be an odd number. - unsafe unsafe_field: u32, +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>, } +``` + +(Note, the `unsafe` field modifier is only applicable to named fields. You should avoid attaching +library safety invariants to unnamed fields.) -// Unsafe field initialization requires an `unsafe` block. -// Safety: `unsafe_field` is odd. -let mut foo = unsafe { - Foo { - safe_field: 0, - unsafe_field: 1, +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 itself enforces that ues of `unsafe` fields that could violate its invariant — i.e., +initializations, writes, references, and reads — must occur within the context of an `unsafe` +block.; e.g.: + +```rust +impl<'a, T> UnalignedRef<'a, T> { + pub fn from_ref(ptr: &'a T) -> Self { + // SAFETY: By invariant on `&T`, `ptr` is a valid and well-aligned instance of `T`. + unsafe { + Self { ptr, _lifetime: PhantomData, } + } } -}; +} +``` -// Safe field: no unsafe block. -foo.safe_field = 1; +...and Clippy's [`undocumented_unsafe_blocks`] lint enforces that the `unsafe` block has a `// +SAFETY` comment. -// Unsafe field with mutation: unsafe block is required. -// Safety: The value is odd. -unsafe { foo.unsafe_field = 3; } +[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/stable/index.html#undocumented_unsafe_blocks -// Unsafe field without mutation: no unsafe block. -println!("{}", foo.unsafe_field); +Using an `unsafe` field outside of the context of an `unsafe` block is an error; e.g., this: + +```rust +struct MaybeInvalidStr<'a> { + /// SAFETY: `maybe_invalid` may not contain valid UTF-8. Nonetheless, it MUST always contain + /// initialized bytes (per language safety invariant on `str`). + pub unsafe maybe_invalid: &'a str +} + +impl<'a> MaybeInvalidStr<'a> { + pub fn as_str(&self) -> &'a str { + self.maybe_invalid + } +} ``` -For a full description of where a mutable access is considered to have occurred (and why), see -[RFC 3323]. Keep in mind that due to reborrowing, a mutable access of an unsafe field is not -necessarily explicit. +...produces this error message: -[RFC 3323]: https://rust-lang.github.io/rfcs/3323-restrictions.html#where-does-a-mutation-occur +``` +error[E0133]: use of unsafe field requires an unsafe block + --> src/main.rs:9:9 + | +9 | self.maybe_invalid + | ^^^^^^^^^^^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants +``` + +## When To Use Unsafe Fields + +You should use the `unsafe` keyword on any field declaration that carries (or relaxes) an invariant +that is assumed to be true by `unsafe` code. + +### Example: Field with Local Invariant + +In the simplest case, a field's safety invariant is a restriction of the invariants imposed by the +field type, and concern only the immediate value of the field; e.g.: ```rust -fn change_unsafe_field(foo: &mut Foo) { - // Safety: An odd number plus two remains an odd number. - unsafe { foo.unsafe_field += 2; } +struct Alignment { + /// SAFETY: `pow` must be between 0 and 29. + pub unsafe pow: u8, } ``` +### Example: Field with Referent Invariant + +A field might carry an invariant with respect to its referent; e.g.: + +```rust +struct CacheArcCount { + /// SAFETY: This `Arc`'s `ref_count` must equal the value of the `ref_count` field. + unsafe arc: Arc, + /// SAFETY: See [`CacheArcCount::arc`]. + unsafe ref_count: usize, +} +``` + +### Example: Field with External Invariant + +A field might carry an invariant with respect to data outside of the Rust abstract machine; e.g.: + +```rust +struct Zeroator { + /// SAFETY: The fd points to a uniquely-owned file, and the bytes from the start of the file to + /// the offset `cursor` (exclusive) are zero. + unsafe fd: OwnedFd, + /// SAFETY: See [`Zeroator::fd`]. + unsafe cursor: usize, +} +``` + +### Example: Field with Suspended Invariant + +A field safety invariant might also be a relaxation of the library safety invariants imposed by the +field type. For example, a `str` is bound by both the language safety invariant that it is +initialized bytes, and by the library safety invariant that it contains valid UTF-8. It is sound to +temporarily violate the library invariant of `str`, so long as the invalid `str` is not safely +exposed to code that assumes `str` validity. + +Below, `MaybeInvalidStr` encapsulates an initialized-but-potentially-invalid `str` as an unsafe +field: + +```rust +struct MaybeInvalidStr<'a> { + /// SAFETY: `maybe_invalid` may not contain valid UTF-8. Nonetheless, it MUST always contain + /// initialized bytes (per language safety invariant on `str`). + pub unsafe maybe_invalid: &'a str +} +``` + +## When *Not* To Use Unsafe Fields + +You should only use the `unsafe` keyword to denote fields whose invariants are relevant to memory +safety. 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, +} +``` + +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. + # Reference-level explanation ## Syntax From bb5b5a7a721cd9a27e36251dba19297b3681cb60 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 8 Jan 2025 17:34:35 +0000 Subject: [PATCH 06/43] RFC3458: Require trivially droppable fields --- text/0000-unsafe-fields.md | 61 +++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 8dceb384cd0..afb8b57da4d 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -219,6 +219,34 @@ error[E0133]: use of unsafe field requires an unsafe block = note: unsafe fields may carry library invariants ``` +Like union fields, `unsafe` struct and enum fields must have trivial destructors. Presently, this +is enforced by requiring that `unsafe` field types are `ManuallyDrop` or implement `Copy`. For +example, this: + +```rust +struct MaybeInvalid { + /// SAFETY: `val` may not uphold the library safety invariants of `T`. You must ensure that + /// uses of `val` do not assume it is a valid `T`. + pub unsafe val: T, +} +``` + +...produces this error message: + +``` +error[E0740]: field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be unsafe + --> src/lib.rs:2:5 + | +2 | pub unsafe val: T, + | ^^^^^^^^^^^^^^^^^ + | + = note: unsafe fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>` +help: wrap the field type in `ManuallyDrop<...>` + | +2 | pub unsafe val: std::mem::ManuallyDrop, + | +++++++++++++++++++++++ + +``` + ## When To Use Unsafe Fields You should use the `unsafe` keyword on any field declaration that carries (or relaxes) an invariant @@ -433,6 +461,27 @@ variants are conceptually unsafe, requiring the programmer to use `unsafe` even of 'safe' fields. This violates [*Tenet: Safe Usage is Usually Safe*](#tenet-safe-usage-is-usually-safe). +### Fields With Non-Trivial Destructors + +We propose that the types of `unsafe` fields should have trivial destructors. Alternatively, we +can imagine permitting field types with non-trivial destructors; e.g.: + +```rust +struct MaybeInvalid { + /// SAFETY: `val` may not uphold the library safety invariants of `T`. You must ensure that + /// subsequent uses of `val` do not assume it is a valid `T`. + pub unsafe val: T, +} +``` + +However, if `T`'s destructor is non-trivial and depends on `T`'s library invariants, then dropping +`val` could induce undefined behavior; this violates [**Tenet: Unsafe Usage is Always +Unsafe**](#tenet-unsafe-usage-is-always-unsafe). + +We adopt union's approach to this problem because it is a conservative, familiar solution that +leaves open the possibility of [future +alternatives](#fields-with-non-copy-or-non-manuallydrop-types). + # Prior art Some items in the Rust standard library have `#[rustc_layout_scalar_valid_range_start]`, @@ -448,4 +497,14 @@ be required to use unsafe fields, which would reduce special-casing of the stand # Future possibilities -?? +## Fields With non-`Copy` or non-`ManuallyDrop` Types + +The conditions that require non-trivial destructors for union fields are not identical to those +that impose the requirement on unsafe struct and enum fields: unions must contend with values that +violate the language safety invariants of their field types; unsafe struct and enum fields contend +merely with violates of library safety invariants. And, whereas unions admit some safe uses +(initializations and writes), unsafe fields do not; this changes the SemVer constraints on the +design space. It might be possible, for example, to permit *any* field type so long as it has a +non-trivial destructor. + +This RFC is forwards-compatible with these possibilities; we leave their design to a future RFC. From d21d32ce4e2eb53520d203ec3a4d8a2956ecde8d Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 8 Jan 2025 19:31:34 +0000 Subject: [PATCH 07/43] RFC3458: `Copy` is conditionally unsafe to implement --- text/0000-unsafe-fields.md | 65 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index afb8b57da4d..106bb8aea61 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -247,6 +247,42 @@ help: wrap the field type in `ManuallyDrop<...>` | +++++++++++++++++++++++ + ``` +The `Copy` trait is unsafe to implement for types with unsafe fields; e.g. this: + +```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 + } +} +``` + +...produces this error message: + +```rust +error[E0200]: the trait `Copy` requires an `unsafe impl` declaration + --> src/lib.rs:9:1 + | +9 | impl<'a, T> Copy for UnalignedMut<'a, T> {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the trait `Copy` cannot be safely implemented for `UnalignedMut<'a, T>` because it has unsafe fields. Review the invariants of those fields before adding an `unsafe impl` +help: add `unsafe` to this trait implementation + | +9 | unsafe impl<'a, T> Copy for UnalignedMut<'a, T> {} + | ++++++ +``` + ## When To Use Unsafe Fields You should use the `unsafe` keyword on any field declaration that carries (or relaxes) an invariant @@ -482,6 +518,35 @@ We adopt union's approach to this problem because it is a conservative, familiar leaves open the possibility of [future alternatives](#fields-with-non-copy-or-non-manuallydrop-types). +### 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). + # Prior art Some items in the Rust standard library have `#[rustc_layout_scalar_valid_range_start]`, From 7dda4468812bb0dd9b63b438b2a560c44b741441 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 9 Jan 2025 15:23:49 +0000 Subject: [PATCH 08/43] RFC3458: Update Reference-Level Explanation Removes last mention of 'mutable use' constraint and syntactic support for unsafe unnamed fields. --- text/0000-unsafe-fields.md | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 106bb8aea61..56f8f5ef21f 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -378,10 +378,8 @@ function of programmer preference and API requirements. ## Syntax -Using the syntax from [the reference for structs][struct syntax], the change needed to support -unsafe fields is minimal. - -[struct syntax]: https://doc.rust-lang.org/stable/reference/items/structs.html#structs +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 : @@ -389,26 +387,12 @@ StructField : Visibility? + unsafe? IDENTIFIER : Type - -TupleField : - OuterAttribute* - Visibility? -+ unsafe? - Type ``` -## Behavior - -An unsafe field may only be mutated or initialized in an unsafe context. Failure to do so is a compile error. - -## "Mutable use" in the compiler - -The concept of a "mutable use" [already exists][mutating use] within the compiler. This catches all -situations that are relevant here, including `ptr::addr_of_mut!`, `&mut`, and direct assignment to a -field, while excluding interior mutability. As such, formal semantics of what constitutes a "mutable -use" are not stated here. +[struct syntax]: https://doc.rust-lang.org/stable/reference/items/structs.html#structs -[mutating use]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/visit/enum.PlaceContext.html#method.is_mutating_use +The use of unsafe fields on unions shall remain forbidden while the [impact of this feature on +unions](#safe-unions) is decided. # Drawbacks @@ -573,3 +557,15 @@ design space. It might be possible, for example, to permit *any* field type so l non-trivial destructor. This RFC is forwards-compatible with these possibilities; we leave their design to a future RFC. + +## Safe Unions + +Unsafe struct and enum fields behave very similarly to union fields — unsafe fields differ only in +that they additionally make initialization and mutation unsafe. Given this closeness, it may be +viable to migrate — across an edition boundary — today's implicitly unsafe unions into *explicitly* +unsafe unions that leverage the unsafe field syntax. + +For example, the 2027 edition could require that all unions leverage the `unsafe` keyword to define +their fields. The 2024-to-2027 migration script would wrap existing initializations and mutations +in `unsafe` blocks annotated with the comment `// SAFETY: No obligations`. In doing so, we would +create syntactic space for *safe* unions in 2030. From 7351f0ecfc6615faa4aae6d4c8dd3da6daa32418 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 9 Jan 2025 20:07:41 +0000 Subject: [PATCH 09/43] RFC3458: Alarm fatigue and its alternatives --- text/0000-unsafe-fields.md | 98 +++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 56f8f5ef21f..0869172126b 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -269,7 +269,7 @@ impl<'a, T> Clone for UnalignedMut<'a, T> { ...produces this error message: -```rust +``` error[E0200]: the trait `Copy` requires an `unsafe impl` declaration --> src/lib.rs:9:1 | @@ -394,11 +394,6 @@ StructField : The use of unsafe fields on unions shall remain forbidden while the [impact of this feature on unions](#safe-unions) is decided. -# Drawbacks - -- Additional syntax for macros to handle -- More syntax to learn - # Rationale and Alternatives The design of this proposal is primarily guided by three tenets: @@ -413,6 +408,10 @@ The design of this proposal is primarily guided by three tenets: 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 with respect to its @@ -531,6 +530,66 @@ However, the `ptr` field introduces a declaration-site safety obligation that is with `unsafe` at any use site; this violates [**Tenet: Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe). +### Suspended Invariants Are Not Supported + +Per [*Tenet: Unsafe Fields Denote Safety +Invariants*](#tenet-unsafe-fields-denote-safety-invariants), this proposal aims to support [fields +with suspended invariants](#example-field-with-suspended-invariant). To achieve this, per [**Tenet: +Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe), reading or referencing +unsafe fields is unsafe. Unsafe fields with suspended invariants are particularly useful for +implementing builders, where the type-to-be-built can be embedded in its builder as an unsafe field +with suspended invariants. + +Providing this support comes at the detriment of [**Tenet: Safe Usage is Usually +Safe**](#tenet-safe-usage-is-usually-safe); even in cases where a field's safety invariant cannot +be violated by a read or reference, the programmer will nonetheless need to enclose the operation +in an `unsafe` block. Alternatively, we could elect to not support this kind of invariant and its +attendant use-cases. + +Programmers working with suspended invariants could still mark those fields as `unsafe` and would +need to continue to encapsulate those fields using Rust's visibility mechanisms. In turn, Rust's +safety hygiene warn against some dangerous usage (e.g., initialization and references) but not +reads. + +This alternative reduces the utility of unsafe fields, the reliability of its tooling, and +complicates Rust's safety story. For these reasons, this proposal favors supporting suspended +invariants. We believe that future, incremental progress can be made towards [**Tenet: Safe Usage +is Usually Safe**](#tenet-safe-usage-is-usually-safe) via [type-directed +analyses](#safe-reads-for-fields-with-local-non-suspended-invariants) or syntactic extensions. + +# Drawbacks + +## Alarm Fatigue + +Although the `unsafe` keyword gives Rust's safety hygiene tooling insight into whether a field +carries safety invariants, it does not give Rust deeper insight into the semantics of those +invariants. Consequently, Rust must err on the side caution, requiring `unsafe` for most uses of +unsafe field — including uses that the programmer can see are conceptually harmless. + +In these cases, Rust's safety hygiene tooling will suggest that the harmless operation is wrapped +in an `unsafe` block, and the programmer will either: + +- comply and provide a trivial safety proof, or +- opt out of Rust's field safety tooling by removing the `unsafe` modifier from their field. + +The former is a private annoyance; the latter is a rebuttal of Rust's safety hygiene conventions +and tooling. Such rebuttals are not unprecedented in the Rust ecosystem. Even among prominent +projects, it is not rare to find a conceptually unsafe function 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 convention of using visibility to enforce +field safety invariants. + +This RFC does not, itself, attempt to address alarm fatigue. Instead, we propose a simple extension +to Rust's safety tooling that is, by virtue of its simplicity, particularly amenable to future +iterative refinement. We imagine empowering Rust to reason about safety invariants, either via +[type-directed analyses](#safe-reads-for-fields-with-local-non-suspended-invariants), or via +syntactic extensions. The design of these refinements will be guided by the valuable usage data +produced by implementing this RFC. + # Prior art Some items in the Rust standard library have `#[rustc_layout_scalar_valid_range_start]`, @@ -558,6 +617,33 @@ non-trivial destructor. This RFC is forwards-compatible with these possibilities; we leave their design to a future RFC. +## Safe Reads For Fields With Local, Non-Suspended Invariants + +To uphold [**Tenet: Safe Usage is Usually Safe**](#tenet-safe-usage-is-usually-safe) and reduce +[alarm fatigue](#alarm-fatigue), future work should seek to empower Rust's to reason about safety +field invariants. In doing so, operations which are obviously harmless to the programmer can also +be made lexically safe. + +Fields with local, non-suspended invariants are, potentially, always safe to read. For example, +reading the `pow` field from `Alignment` cannot possibly violate its invariants: + +```rust +struct Alignment { + /// SAFETY: `pow` must be between 0 and 29. + pub unsafe pow: u8, +} +``` + +Outside of the context of `Alignment`, `u8` has no special meaning. It has no library safety +invariants (and thus no library safety invariants that might be suspended by the field `pow`), and +it is not a pointer or handle to another resource. + +The set of safe-to-read types, $S$, includes (but is not limited to): +- primitive numeric types +- public, compound types with public constructors whose members are in $S$. + +A type-directed analysis could make reads of these field types safe. + ## Safe Unions Unsafe struct and enum fields behave very similarly to union fields — unsafe fields differ only in From eb6969ae0a8c9d9b42a46e7f0fcce01ec7ecbd37 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 25 Feb 2025 18:56:23 +0000 Subject: [PATCH 10/43] RFC3458: Reduce scope to additive invariants See additions to Alternatives for rationale. --- text/0000-unsafe-fields.md | 322 +++++++++++++++++-------------------- 1 file changed, 152 insertions(+), 170 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 0869172126b..b8910a7bbf5 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -6,8 +6,8 @@ # 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.: +additive library safety invariants. Consequently, Rust programmers will be able to use the `unsafe` +keyword to denote when a named field carries an additive library safety invariant; e.g.: ```rust struct UnalignedRef<'a, T> { @@ -19,8 +19,8 @@ struct UnalignedRef<'a, T> { } ``` -Rust will enforce that potentially-invalidating uses of `unsafe` fields only occur in the context -of an `unsafe` block, and Clippy's [`missing_safety_doc`] lint will check that `unsafe` fields have +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 @@ -145,7 +145,7 @@ 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 language +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 @@ -191,101 +191,76 @@ SAFETY` comment. [`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/stable/index.html#undocumented_unsafe_blocks -Using an `unsafe` field outside of the context of an `unsafe` block is an error; e.g., this: +Fields marked `unsafe` may be safely moved and copied; e.g.: ```rust -struct MaybeInvalidStr<'a> { - /// SAFETY: `maybe_invalid` may not contain valid UTF-8. Nonetheless, it MUST always contain - /// initialized bytes (per language safety invariant on `str`). - pub unsafe maybe_invalid: &'a str +struct Alignment { + /// SAFETY: `pow` must be between 0 and 29. + pub unsafe pow: u8, } -impl<'a> MaybeInvalidStr<'a> { - pub fn as_str(&self) -> &'a str { - self.maybe_invalid +impl Alignment { + pub fn as_log(self) -> u8 { + self.pow } } ``` -...produces this error message: - -``` -error[E0133]: use of unsafe field requires an unsafe block - --> src/main.rs:9:9 - | -9 | self.maybe_invalid - | ^^^^^^^^^^^^^^^^^^ use of unsafe field - | - = note: unsafe fields may carry library invariants -``` - -Like union fields, `unsafe` struct and enum fields must have trivial destructors. Presently, this -is enforced by requiring that `unsafe` field types are `ManuallyDrop` or implement `Copy`. For -example, this: +...but all other uses, including initialization and referencing, require `unsafe`; e.g., this: ```rust -struct MaybeInvalid { - /// SAFETY: `val` may not uphold the library safety invariants of `T`. You must ensure that - /// uses of `val` do not assume it is a valid `T`. - pub unsafe val: T, +struct CacheArcCount { + /// SAFETY: This `Arc`'s `ref_count` must equal the + /// value of the `ref_count` field. + unsafe arc: Arc, + /// SAFETY: See [`CacheArcCount::arc`]. + unsafe ref_count: usize, } -``` - -...produces this error message: -``` -error[E0740]: field must implement `Copy` or be wrapped in `ManuallyDrop<...>` to be unsafe - --> src/lib.rs:2:5 - | -2 | pub unsafe val: T, - | ^^^^^^^^^^^^^^^^^ - | - = note: unsafe fields must not have drop side-effects, which is currently enforced via either `Copy` or `ManuallyDrop<...>` -help: wrap the field type in `ManuallyDrop<...>` - | -2 | pub unsafe val: std::mem::ManuallyDrop, - | +++++++++++++++++++++++ + -``` - -The `Copy` trait is unsafe to implement for types with unsafe fields; e.g. this: - -```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 CacheArcCount { + fn new(value: T) -> Self { + Self { + arc: Arc::new(value), + ref_count: 1, + } + } } -impl<'a, T> Copy for UnalignedMut<'a, T> {} +impl core::ops::Deref for CacheArcCount { + type Target = Arc; -impl<'a, T> Clone for UnalignedMut<'a, T> { - fn clone(&self) -> Self { - *self + fn deref(&self) -> &Self::Target { + &self.arc } } ``` -...produces this error message: +...produces the errors: ``` -error[E0200]: the trait `Copy` requires an `unsafe impl` declaration - --> src/lib.rs:9:1 - | -9 | impl<'a, T> Copy for UnalignedMut<'a, T> {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: the trait `Copy` cannot be safely implemented for `UnalignedMut<'a, T>` because it has unsafe fields. Review the invariants of those fields before adding an `unsafe impl` -help: add `unsafe` to this trait implementation - | -9 | unsafe impl<'a, T> Copy for UnalignedMut<'a, T> {} - | ++++++ +error[E0133]: initializing type with an unsafe field is unsafe and requires unsafe block + --> src/lib.rs:11:9 + | +11 | / Self { +12 | | arc: Arc::new(value), +13 | | ref_count: 1, +14 | | } + | |_________^ 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:22:10 + | +22 | &self.arc + | ^^^^^^^^ use of unsafe field + | + = note: unsafe fields may carry library invariants ``` ## When To Use Unsafe Fields -You should use the `unsafe` keyword on any field declaration that carries (or relaxes) an invariant +You should use the `unsafe` keyword on any field declaration that carries an invariant that is assumed to be true by `unsafe` code. ### Example: Field with Local Invariant @@ -300,9 +275,9 @@ struct Alignment { } ``` -### Example: Field with Referent Invariant +### Example: Field with Remote Invariant -A field might carry an invariant with respect to its referent; e.g.: +A field might carry an invariant with respect to data beyond its immediate bytes; e.g. beyond a reference: ```rust struct CacheArcCount { @@ -313,9 +288,7 @@ struct CacheArcCount { } ``` -### Example: Field with External Invariant - -A field might carry an invariant with respect to data outside of the Rust abstract machine; e.g.: +...or even beyond the Rust abstract machine; e.g.: ```rust struct Zeroator { @@ -327,13 +300,15 @@ struct Zeroator { } ``` +## When *Not* To Use Unsafe Fields + ### Example: Field with Suspended Invariant -A field safety invariant might also be a relaxation of the library safety invariants imposed by the -field type. For example, a `str` is bound by both the language safety invariant that it is -initialized bytes, and by the library safety invariant that it contains valid UTF-8. It is sound to -temporarily violate the library invariant of `str`, so long as the invalid `str` is not safely -exposed to code that assumes `str` validity. +A field might be unsafe because it *relaxes* the library safety invariants imposed by its type. For +example, a `str` is bound by both the language safety invariant that it is initialized bytes, and by +the library safety invariant that it contains valid UTF-8. It is sound to temporarily violate the +library invariant of `str`, so long as the invalid `str` is not safely exposed to code that assumes +`str` validity. Below, `MaybeInvalidStr` encapsulates an initialized-but-potentially-invalid `str` as an unsafe field: @@ -342,15 +317,31 @@ field: struct MaybeInvalidStr<'a> { /// SAFETY: `maybe_invalid` may not contain valid UTF-8. Nonetheless, it MUST always contain /// initialized bytes (per language safety invariant on `str`). - pub unsafe maybe_invalid: &'a str + unsafe maybe_invalid: &'a str } ``` -## When *Not* To Use Unsafe Fields +However, the `unsafe` modifier, above, is insufficient to prevent invalid use in safe contexts; e.g. +Rust accepts this, without error: + +```rust +impl<'a> core::ops::Deref for MaybeInvalidStr<'a> { + type Target = str; + + fn deref(&self) -> &str { + self.maybe_invalid + } +} +``` + +The `unsafe` keyword, alone, cannot be relied upon to ensure that invalid use of such fields cannot +occur in safe contexts. -You should only use the `unsafe` keyword to denote fields whose invariants are relevant to memory -safety. In the below example, unsafe code may rely upon `alignment_pow`s invariant, but not -`size`'s invariant: +### Example: Field with 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 { @@ -370,6 +361,9 @@ struct Layout { } ``` +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. @@ -414,8 +408,7 @@ cost, [alarm fatigue](#alarm-fatigue). The third tenet implores us to weigh this ## Tenet: Unsafe Fields Denote Safety Invariants -> A field *should* be marked `unsafe` if it carries library safety invariants with respect to its -> enclosing type. +> 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 @@ -480,27 +473,6 @@ variants are conceptually unsafe, requiring the programmer to use `unsafe` even of 'safe' fields. This violates [*Tenet: Safe Usage is Usually Safe*](#tenet-safe-usage-is-usually-safe). -### Fields With Non-Trivial Destructors - -We propose that the types of `unsafe` fields should have trivial destructors. Alternatively, we -can imagine permitting field types with non-trivial destructors; e.g.: - -```rust -struct MaybeInvalid { - /// SAFETY: `val` may not uphold the library safety invariants of `T`. You must ensure that - /// subsequent uses of `val` do not assume it is a valid `T`. - pub unsafe val: T, -} -``` - -However, if `T`'s destructor is non-trivial and depends on `T`'s library invariants, then dropping -`val` could induce undefined behavior; this violates [**Tenet: Unsafe Usage is Always -Unsafe**](#tenet-unsafe-usage-is-always-unsafe). - -We adopt union's approach to this problem because it is a conservative, familiar solution that -leaves open the possibility of [future -alternatives](#fields-with-non-copy-or-non-manuallydrop-types). - ### Copy Is Safe To Implement We propose that `Copy` is conditionally unsafe to implement; i.e., that the `unsafe` modifier is @@ -530,32 +502,62 @@ However, the `ptr` field introduces a declaration-site safety obligation that is with `unsafe` at any use site; this violates [**Tenet: Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe). -### Suspended Invariants Are Not Supported - -Per [*Tenet: Unsafe Fields Denote Safety -Invariants*](#tenet-unsafe-fields-denote-safety-invariants), this proposal aims to support [fields -with suspended invariants](#example-field-with-suspended-invariant). To achieve this, per [**Tenet: -Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe), reading or referencing -unsafe fields is unsafe. Unsafe fields with suspended invariants are particularly useful for -implementing builders, where the type-to-be-built can be embedded in its builder as an unsafe field -with suspended invariants. - -Providing this support comes at the detriment of [**Tenet: Safe Usage is Usually -Safe**](#tenet-safe-usage-is-usually-safe); even in cases where a field's safety invariant cannot -be violated by a read or reference, the programmer will nonetheless need to enclose the operation -in an `unsafe` block. Alternatively, we could elect to not support this kind of invariant and its -attendant use-cases. - -Programmers working with suspended invariants could still mark those fields as `unsafe` and would -need to continue to encapsulate those fields using Rust's visibility mechanisms. In turn, Rust's -safety hygiene warn against some dangerous usage (e.g., initialization and references) but not -reads. - -This alternative reduces the utility of unsafe fields, the reliability of its tooling, and -complicates Rust's safety story. For these reasons, this proposal favors supporting suspended -invariants. We believe that future, incremental progress can be made towards [**Tenet: Safe Usage -is Usually Safe**](#tenet-safe-usage-is-usually-safe) via [type-directed -analyses](#safe-reads-for-fields-with-local-non-suspended-invariants) or syntactic extensions. +### Suspended Invariants Are Supported + +To provide sound safety tooling for fields with subtractive invariants, Rust must enforce that such +fields have trivial destructors, à la union fields. We initially considered this requirement to be +merely inconvenient — if we followed the same enforcement regime as unions, `unsafe` fields would +either need to be `Copy` or wrapped in `ManuallyDrop`. + +In fact, we discovered, 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, +} +``` + +...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>, +} +``` + +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. + +The library author cannot use `unsafe` to denote that this field carries a safety invariant; +consequently, a one-size-fits-all design violates our first design tenet: *a field must be marked +`unsafe` if it carries a safety invariant*. # Drawbacks @@ -605,44 +607,24 @@ be required to use unsafe fields, which would reduce special-casing of the stand # Future possibilities -## Fields With non-`Copy` or non-`ManuallyDrop` Types +## Tooling for Fields with Suspended Safety Invariants -The conditions that require non-trivial destructors for union fields are not identical to those -that impose the requirement on unsafe struct and enum fields: unions must contend with values that -violate the language safety invariants of their field types; unsafe struct and enum fields contend -merely with violates of library safety invariants. And, whereas unions admit some safe uses -(initializations and writes), unsafe fields do not; this changes the SemVer constraints on the -design space. It might be possible, for example, to permit *any* field type so long as it has a -non-trivial destructor. - -This RFC is forwards-compatible with these possibilities; we leave their design to a future RFC. - -## Safe Reads For Fields With Local, Non-Suspended Invariants - -To uphold [**Tenet: Safe Usage is Usually Safe**](#tenet-safe-usage-is-usually-safe) and reduce -[alarm fatigue](#alarm-fatigue), future work should seek to empower Rust's to reason about safety -field invariants. In doing so, operations which are obviously harmless to the programmer can also -be made lexically safe. - -Fields with local, non-suspended invariants are, potentially, always safe to read. For example, -reading the `pow` field from `Alignment` cannot possibly violate its invariants: +We will additionally explore supporting fields with suspended safety invariants. Speculatively, this +might take the form of an additional modifier; e.g.: ```rust -struct Alignment { - /// SAFETY: `pow` must be between 0 and 29. - pub unsafe pow: u8, +struct MaybeInvalidStr<'a> { + /// SAFETY: `maybe_invalid` may not contain valid UTF-8. Nonetheless, it MUST always contain + /// initialized bytes (per language safety invariant on `str`). + unsafe(relaxed) maybe_invalid: &'a str } ``` -Outside of the context of `Alignment`, `u8` has no special meaning. It has no library safety -invariants (and thus no library safety invariants that might be suspended by the field `pow`), and -it is not a pointer or handle to another resource. - -The set of safe-to-read types, $S$, includes (but is not limited to): -- primitive numeric types -- public, compound types with public constructors whose members are in $S$. +...which would convey that Rust must also treat moves and copies of such fields as unsafe. -A type-directed analysis could make reads of these field types safe. +Alternatively, this use-case might be addressed by introducing a designated `MaybeInvalid` +wrapper in the standard library, which encapsulates `T` and provides only `unsafe` accessor +functions. ## Safe Unions From 5167009b3985c1e2409c896b6a497dcd5bce152d Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 25 Feb 2025 18:57:55 +0000 Subject: [PATCH 11/43] RFC3458: Add depth to 'Safe Unions' future possibility --- text/0000-unsafe-fields.md | 46 ++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index b8910a7bbf5..8605a65b3fe 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -628,12 +628,40 @@ functions. ## Safe Unions -Unsafe struct and enum fields behave very similarly to union fields — unsafe fields differ only in -that they additionally make initialization and mutation unsafe. Given this closeness, it may be -viable to migrate — across an edition boundary — today's implicitly unsafe unions into *explicitly* -unsafe unions that leverage the unsafe field syntax. - -For example, the 2027 edition could require that all unions leverage the `unsafe` keyword to define -their fields. The 2024-to-2027 migration script would wrap existing initializations and mutations -in `unsafe` blocks annotated with the comment `// SAFETY: No obligations`. In doing so, we would -create syntactic space for *safe* unions in 2030. +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 { + uninit: (), + unsafe(invalid) value: ManuallyDrop, +} +``` + +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. From f14a6fd1dbd9ff9592599b43718185123c937e03 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 26 Feb 2025 18:07:27 +0000 Subject: [PATCH 12/43] RFC3458: Allow subtractive invariants by constraining safety hygiene Adopts Ralf's proposed framing that `unsafe` may be used for any kind of safety invariant, but that it is a serious breach of good safety hygiene to make the invariant weaker than what the destructor of the field requires. See https://github.com/rust-lang/rfcs/pull/3458#discussion_r1971676100 --- text/0000-unsafe-fields.md | 272 ++++++++++++++++++++++--------------- 1 file changed, 162 insertions(+), 110 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 8605a65b3fe..a3bf9695315 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -6,8 +6,8 @@ # Summary This RFC proposes extending Rust's tooling support for safety hygiene to named fields that carry -additive library safety invariants. Consequently, Rust programmers will be able to use the `unsafe` -keyword to denote when a named field carries an additive library safety invariant; e.g.: +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> { @@ -166,102 +166,128 @@ pub struct UnalignedRef<'a, T> { } ``` -(Note, the `unsafe` field modifier is only applicable to named fields. You should avoid attaching -library safety invariants to unnamed fields.) +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 itself enforces that ues of `unsafe` fields that could violate its invariant — i.e., -initializations, writes, references, and reads — must occur within the context of an `unsafe` -block.; e.g.: +[`missing_safety_doc`] lint checks that `unsafe` fields have accompanying safety documentation. -```rust -impl<'a, T> UnalignedRef<'a, T> { - pub fn from_ref(ptr: &'a T) -> Self { - // SAFETY: By invariant on `&T`, `ptr` is a valid and well-aligned instance of `T`. - unsafe { - Self { ptr, _lifetime: PhantomData, } - } - } -} -``` - -...and Clippy's [`undocumented_unsafe_blocks`] lint enforces that the `unsafe` block has a `// -SAFETY` comment. - -[`undocumented_unsafe_blocks`]: https://rust-lang.github.io/rust-clippy/stable/index.html#undocumented_unsafe_blocks - -Fields marked `unsafe` may be safely moved and copied; e.g.: +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)] + struct Alignment { - /// SAFETY: `pow` must be between 0 and 29. + /// SAFETY: `pow` must be between 0 and 29 (inclusive). pub unsafe pow: u8, } impl Alignment { - pub fn as_log(self) -> u8 { - self.pow - } -} -``` - -...but all other uses, including initialization and referencing, require `unsafe`; e.g., this: - -```rust -struct CacheArcCount { - /// SAFETY: This `Arc`'s `ref_count` must equal the - /// value of the `ref_count` field. - unsafe arc: Arc, - /// SAFETY: See [`CacheArcCount::arc`]. - unsafe ref_count: usize, -} - -impl CacheArcCount { - fn new(value: T) -> Self { - Self { - arc: Arc::new(value), - ref_count: 1, + pub fn new(pow: u8) -> Option { + if pow > 29 { + return None; } + + Some(Self { pow }) } -} -impl core::ops::Deref for CacheArcCount { - type Target = Arc; + pub fn as_log(self) -> u8 { + self.pow + } - fn deref(&self) -> &Self::Target { - &self.arc + /// # 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 } } ``` -...produces the errors: +...emits the errors: ``` error[E0133]: initializing type with an unsafe field is unsafe and requires unsafe block - --> src/lib.rs:11:9 + --> 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 | -11 | / Self { -12 | | arc: Arc::new(value), -13 | | ref_count: 1, -14 | | } - | |_________^ initialization of struct with unsafe field +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:22:10 + --> src/lib.rs:25:14 | -22 | &self.arc - | ^^^^^^^^ use of unsafe field +25 | &mut self.pow + | ^^^^^^^^ use of unsafe field | + = note: for more information, see = 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)] + + struct Alignment { + /// SAFETY: `pow` must be between 0 and 29 (inclusive). + pub unsafe pow: u8, + } + + impl Alignment { + pub fn new(pow: u8) -> Option { + 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 } + } + } ``` ## When To Use Unsafe Fields -You should use the `unsafe` keyword on any field declaration that carries an invariant -that is assumed to be true by `unsafe` code. +You should use the `unsafe` keyword on any that carries a safety invariant, but you may never make +the invariant weaker than what the destructor of the field requires. ### Example: Field with Local Invariant @@ -300,13 +326,14 @@ struct Zeroator { } ``` -## When *Not* To Use Unsafe Fields +### Example: Field with a Subtractive Invariant -### Example: Field with Suspended Invariant +You may use the `unsafe` modifier to denote that a field *relaxes* the invariant imposed byte its +type, so long as you do not relax that invariant beyond what is required to soundly run the field's +destructor. -A field might be unsafe because it *relaxes* the library safety invariants imposed by its type. For -example, a `str` is bound by both the language safety invariant that it is initialized bytes, and by -the library safety invariant that it contains valid UTF-8. It is sound to temporarily violate the +For example, a `str` is both trivially destructable (because it implements `Copy`), and bound by the +library safety invariant that it contains valid UTF-8. It is sound to temporarily violate the library invariant of `str`, so long as the invalid `str` is not safely exposed to code that assumes `str` validity. @@ -321,21 +348,56 @@ struct MaybeInvalidStr<'a> { } ``` -However, the `unsafe` modifier, above, is insufficient to prevent invalid use in safe contexts; e.g. -Rust accepts this, without error: +## When *Not* To Use Unsafe Fields + +### Example: Field with a Subtractive Invariant and Drop Glue + +Although you may use the `unsafe` modifier to denote that a field relaxes its type's invariant, you +must never relax that invariant beyond what is required to run its destructor. + +For example, `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 +which violates this invariant; e.g.: ```rust -impl<'a> core::ops::Deref for MaybeInvalidStr<'a> { - type Target = str; +struct BoxedErased { + /// SAFETY: `data`'s logical type has `type_id`. + unsafe data: Box<[MaybeUninit]>, + /// SAFETY: See [`BoxErased::data`]. + unsafe type_id: TypeId, +} - fn deref(&self) -> &str { - self.maybe_invalid +impl BoxedErased { + fn new(src: Box) -> Self { + let data = …; // cast `Box` to `Box<[MaybeUninit]>` + let type_id = TypeId::of::; + // SAFETY: … + unsafe { + BoxedErased { + data, + type_id, + } + } } } ``` -The `unsafe` keyword, alone, cannot be relied upon to ensure that invalid use of such fields cannot -occur in safe contexts. +...is insufficent for ensuring 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]>, + /// SAFETY: See [`BoxErased::data`]. ++ unsafe data: ManuallyDrop]>>, + unsafe type_id: TypeId, + } +``` ### Example: Field with Correctness Invariant @@ -502,16 +564,25 @@ However, the `ptr` field introduces a declaration-site safety obligation that is with `unsafe` at any use site; this violates [**Tenet: Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe). -### Suspended Invariants Are Supported +### 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. -To provide sound safety tooling for fields with subtractive invariants, Rust must enforce that such -fields have trivial destructors, à la union fields. We initially considered this requirement to be -merely inconvenient — if we followed the same enforcement regime as unions, `unsafe` fields would -either need to be `Copy` or wrapped in `ManuallyDrop`. +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`. -In fact, we discovered, 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: +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 { @@ -553,11 +624,11 @@ impl Drop for SafeAbstraction { ``` This is a SemVer-breaking change. If the library author goes though with this, the aforementioned -downstream code will no longer compile. - -The library author cannot use `unsafe` to denote that this field carries a safety invariant; -consequently, a one-size-fits-all design violates our first design tenet: *a field must be marked -`unsafe` if it carries a safety invariant*. +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). # Drawbacks @@ -607,25 +678,6 @@ be required to use unsafe fields, which would reduce special-casing of the stand # Future possibilities -## Tooling for Fields with Suspended Safety Invariants - -We will additionally explore supporting fields with suspended safety invariants. Speculatively, this -might take the form of an additional modifier; e.g.: - -```rust -struct MaybeInvalidStr<'a> { - /// SAFETY: `maybe_invalid` may not contain valid UTF-8. Nonetheless, it MUST always contain - /// initialized bytes (per language safety invariant on `str`). - unsafe(relaxed) maybe_invalid: &'a str -} -``` - -...which would convey that Rust must also treat moves and copies of such fields as unsafe. - -Alternatively, this use-case might be addressed by introducing a designated `MaybeInvalid` -wrapper in the standard library, which encapsulates `T` and provides only `unsafe` accessor -functions. - ## Safe Unions Today, unions provide language support for fields with subtractive *language* invariants. Unions may From cfea60f7fdcd018fc8ecb18938419d8fc7676b37 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 26 Feb 2025 18:37:11 +0000 Subject: [PATCH 13/43] RFC3458: Clarify that language safety invariants must never be violated See: https://github.com/rust-lang/rfcs/pull/3458#discussion_r1972123674 --- text/0000-unsafe-fields.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index a3bf9695315..292411537fa 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -286,8 +286,8 @@ For more information about this error, try `rustc --explain E0133`. ## When To Use Unsafe Fields -You should use the `unsafe` keyword on any that carries a safety invariant, but you may never make -the invariant weaker than what the destructor of the field requires. +You should use the `unsafe` keyword on any that carries a library safety invariant, but you may +never make the invariant weaker than what the destructor of the field requires. ### Example: Field with Local Invariant @@ -350,6 +350,27 @@ struct MaybeInvalidStr<'a> { ## When *Not* To Use Unsafe Fields +### Example: 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 { + // SAFETY: The value of `zeroed` consists only of bytes initialized to `0`. + unsafe zeroed: T, +} + +impl Zeroed { + pub fn zeroed() -> Self { + unsafe { Self { zeroed: core::mem::zeroed() }} + } +} +``` + +...because `Zeroed::::zeroed()` induces undefined behavior. + ### Example: Field with a Subtractive Invariant and Drop Glue Although you may use the `unsafe` modifier to denote that a field relaxes its type's invariant, you From 0b678cad8adce3004e66446327bf76bd3d1d80c1 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 26 Feb 2025 19:28:46 +0000 Subject: [PATCH 14/43] RFC3458: Clarify that terminology used in RFC is unresolved. This is an RFC about unsafe fields; not for establishing terminology. --- text/0000-unsafe-fields.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 292411537fa..542c7ebcb2c 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -695,7 +695,15 @@ be required to use unsafe fields, which would reduce special-casing of the stand - If the syntax for restrictions does not change, what is the ordering of keywords on a field that is both unsafe and mut-restricted? -- Are there any interactions or edge cases with other language features that need to be considered? + +## 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 From c848c59fb8667d54d2533cc25c204bfab270e925 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 27 Feb 2025 18:41:30 +0000 Subject: [PATCH 15/43] RFC3458: Fix typos See https://github.com/rust-lang/rfcs/pull/3458#pullrequestreview-2645848219 --- text/0000-unsafe-fields.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 542c7ebcb2c..d7d74e5febf 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -179,7 +179,7 @@ block. For example, compiling this program: ```rust #![forbid(unsafe_op_in_unsafe_fn)] -struct Alignment { +pub struct Alignment { /// SAFETY: `pow` must be between 0 and 29 (inclusive). pub unsafe pow: u8, } @@ -200,7 +200,7 @@ impl Alignment { /// # 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 { + pub unsafe fn as_mut_log(&mut self) -> &mut u8 { &mut self.pow } } @@ -252,7 +252,7 @@ For more information about this error, try `rustc --explain E0133`. ```diff #![forbid(unsafe_op_in_unsafe_fn)] - struct Alignment { + pub struct Alignment { /// SAFETY: `pow` must be between 0 and 29 (inclusive). pub unsafe pow: u8, } @@ -286,8 +286,8 @@ For more information about this error, try `rustc --explain E0133`. ## When To Use Unsafe Fields -You should use the `unsafe` keyword on any that carries a library safety invariant, but you may -never make the invariant weaker than what the destructor of the field requires. +You should use the `unsafe` keyword on any field that carries a library safety invariant, but you +may never make the invariant weaker than what the destructor of the field requires. ### Example: Field with Local Invariant @@ -295,7 +295,7 @@ In the simplest case, a field's safety invariant is a restriction of the invaria field type, and concern only the immediate value of the field; e.g.: ```rust -struct Alignment { +pub struct Alignment { /// SAFETY: `pow` must be between 0 and 29. pub unsafe pow: u8, } @@ -328,8 +328,8 @@ struct Zeroator { ### Example: Field with a Subtractive Invariant -You may use the `unsafe` modifier to denote that a field *relaxes* the invariant imposed byte its -type, so long as you do not relax that invariant beyond what is required to soundly run the field's +You may use the `unsafe` modifier to denote that a field *relaxes* the invariant imposed by its type +so long as you do not relax that invariant beyond what is required to soundly run the field's destructor. For example, a `str` is both trivially destructable (because it implements `Copy`), and bound by the @@ -447,7 +447,7 @@ struct Layout { 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 +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. From 916feb4f89ad9e1b13f90a8296c071ebfa490b52 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 28 Feb 2025 19:09:26 +0000 Subject: [PATCH 16/43] RFC3458: Fields must be soundly droppable before being dropped See: https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/unsafe.20fields.20RFC/near/502331190 --- text/0000-unsafe-fields.md | 128 +++++++++++-------------------------- 1 file changed, 38 insertions(+), 90 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index d7d74e5febf..1231a33224b 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -166,6 +166,9 @@ pub struct UnalignedRef<'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. @@ -284,101 +287,23 @@ For more information about this error, try `rustc --explain E0133`. } ``` -## When To Use Unsafe Fields - -You should use the `unsafe` keyword on any field that carries a library safety invariant, but you -may never make the invariant weaker than what the destructor of the field requires. - -### Example: Field with Local Invariant - -In the simplest case, a field's safety invariant is a restriction of the invariants imposed by the -field type, and concern only the immediate value of the field; e.g.: - -```rust -pub struct Alignment { - /// SAFETY: `pow` must be between 0 and 29. - pub unsafe pow: u8, -} -``` - -### Example: Field with Remote Invariant - -A field might carry an invariant with respect to data beyond its immediate bytes; e.g. beyond a reference: +You may use `unsafe` to denote that a type relaxes its type's library safety invariant; e.g.: ```rust -struct CacheArcCount { - /// SAFETY: This `Arc`'s `ref_count` must equal the value of the `ref_count` field. - unsafe arc: Arc, - /// SAFETY: See [`CacheArcCount::arc`]. - unsafe ref_count: usize, -} -``` - -...or even beyond the Rust abstract machine; e.g.: - -```rust -struct Zeroator { - /// SAFETY: The fd points to a uniquely-owned file, and the bytes from the start of the file to - /// the offset `cursor` (exclusive) are zero. - unsafe fd: OwnedFd, - /// SAFETY: See [`Zeroator::fd`]. - unsafe cursor: usize, -} -``` - -### Example: Field with a Subtractive Invariant - -You may use the `unsafe` modifier to denote that a field *relaxes* the invariant imposed by its type -so long as you do not relax that invariant beyond what is required to soundly run the field's -destructor. - -For example, a `str` is both trivially destructable (because it implements `Copy`), and bound by the -library safety invariant that it contains valid UTF-8. It is sound to temporarily violate the -library invariant of `str`, so long as the invalid `str` is not safely exposed to code that assumes -`str` validity. - -Below, `MaybeInvalidStr` encapsulates an initialized-but-potentially-invalid `str` as an unsafe -field: - -```rust -struct MaybeInvalidStr<'a> { +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: &'a str -} -``` - -## When *Not* To Use Unsafe Fields - -### Example: 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 { - // SAFETY: The value of `zeroed` consists only of bytes initialized to `0`. - unsafe zeroed: T, -} - -impl Zeroed { - pub fn zeroed() -> Self { - unsafe { Self { zeroed: core::mem::zeroed() }} - } + unsafe maybe_invalid: str } ``` -...because `Zeroed::::zeroed()` induces undefined behavior. - -### Example: Field with a Subtractive Invariant and Drop Glue +...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. -Although you may use the `unsafe` modifier to denote that a field relaxes its type's invariant, you -must never relax that invariant beyond what is required to run its destructor. - -For example, `Box` has a non-trivial destructor which requires that its referent has the same size +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 -which violates this invariant; e.g.: +that violates this invariant; e.g.: ```rust struct BoxedErased { @@ -403,9 +328,9 @@ impl BoxedErased { } ``` -...is insufficent for ensuring 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. +...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.: @@ -420,7 +345,30 @@ field in `ManuallyDrop`; e.g.: } ``` -### Example: Field with Correctness Invariant +## 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 { + // SAFETY: The value of `zeroed` consists only of bytes initialized to `0`. + unsafe zeroed: T, +} + +impl Zeroed { + pub fn zeroed() -> Self { + unsafe { Self { zeroed: core::mem::zeroed() }} + } +} +``` + +...because `Zeroed::::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, From 667a3de7dcb5e62becb423f978147ad9e84716ce Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 2 Apr 2025 18:17:34 +0000 Subject: [PATCH 17/43] RFC3458: Add complete, realistic example --- text/0000-unsafe-fields.md | 124 +++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 1231a33224b..7c4eded9829 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -399,6 +399,130 @@ We might also imagine a variant of the above example where `alignment_pow`, like 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 { + /// # Safety + /// + /// So long as `T` is owned by `UniqueArc`, `T` may not be accessed (read or + /// written) other than via this `UniqueArc`. + unsafe arc: Arc>, +} + +/// Keeps the parent [`UniqueArc`] alive without providing read or write access +/// to its value. +pub struct KeepAlive { + /// # Safety + /// + /// `T` may not be accessed (read or written) via this `Arc`. + #[expect(unused)] + unsafe arc: Arc>, +} + +impl UniqueArc { + /// 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 { + // 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 { + // 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 Deref for UniqueArc { + 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`, `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 DerefMut for UniqueArc { + 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`, `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 } + } +} + +// SAFETY: `UniqueArc` has the same aliasing and synchronization properties +// as `&mut T`, and so it is `Sync` if `&mut T` is `Sync`. +unsafe impl Sync for UniqueArc where &'static mut T: Sync {} +``` + # Reference-level explanation ## Syntax From 21f38a1aef2853a70a0d2b71272a8acaaff6f269 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 2 Apr 2025 20:25:55 +0000 Subject: [PATCH 18/43] RFC3458: Document why field copies and moves require `unsafe` See: - https://github.com/rust-lang/rfcs/pull/3458#discussion_r1970998426 - https://github.com/rust-lang/rfcs/pull/3458#discussion_r2013572553 --- text/0000-unsafe-fields.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 7c4eded9829..4881449f997 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -628,6 +628,40 @@ variants are conceptually unsafe, requiring the programmer to use `unsafe` even 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::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 { + /// # Safety + /// + /// `T` may not be accessed (read or written) via this `Arc`. + unsafe arc: Arc>, +} +``` + +Allowing `arc` to be safely moved out of `KeepAlive` 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` (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` 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 From b1b7aca5c78965812c693ae3c3bccab84907cbfe Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 11 Apr 2025 17:50:28 +0000 Subject: [PATCH 19/43] RFC3458: Explore 'Wrapper Type' and 'More Syntactic Granularity' alternatives. --- text/0000-unsafe-fields.md | 143 +++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 4881449f997..9c349a806ae 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -757,6 +757,149 @@ Unsafe Fields Denote Safety Invariants**](#tenet-unsafe-fields-denote-safety-inv 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); + +impl Unsafe { + +pub unsafe 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. +`Unsafe::new` inherits the safety conditions of the field that the `Unsafe` will be assigned to, and +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 are a product of 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, +} + +struct Odd { + val: Unsafe, +} +``` + +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(PhantomData, 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 { + alloc: Unsafe, + len: Unsafe, + cap: Unsafe, +} +``` + +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 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. + # Drawbacks ## Alarm Fatigue From 1932ace612c64bbbe44f2bebda31e24e422a2e28 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 11 Apr 2025 19:24:52 +0000 Subject: [PATCH 20/43] RFC3458: Tweak `UniqueArc::arc` invariant. See https://github.com/rust-lang/rfcs/pull/3458#discussion_r2034082636 --- text/0000-unsafe-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 9c349a806ae..5cecb39e970 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -426,8 +426,8 @@ use std::{ pub struct UniqueArc { /// # Safety /// - /// So long as `T` is owned by `UniqueArc`, `T` may not be accessed (read or - /// written) other than via this `UniqueArc`. + /// 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>, } From eaadc1e936dfe5a76251092f2834b3b7bb20ecd9 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 11 Apr 2025 19:33:10 +0000 Subject: [PATCH 21/43] RFC3458: Tweak `Sync` impl for `UniqueArc` See https://github.com/rust-lang/rfcs/pull/3458#discussion_r2028533587 --- text/0000-unsafe-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 5cecb39e970..3a03e53c1c9 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -520,7 +520,7 @@ impl DerefMut for UniqueArc { // SAFETY: `UniqueArc` has the same aliasing and synchronization properties // as `&mut T`, and so it is `Sync` if `&mut T` is `Sync`. -unsafe impl Sync for UniqueArc where &'static mut T: Sync {} +unsafe impl Sync for UniqueArc where for<'a> &'a mut T: Sync {} ``` # Reference-level explanation From e6cc51f08cd2d3b7c4d4babf814c67d92175fa80 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 15 Apr 2025 12:22:14 +0000 Subject: [PATCH 22/43] RFC3458: Remove problematic `Sync` impl See https://github.com/rust-lang/rfcs/pull/3458#discussion_r2040230277 --- text/0000-unsafe-fields.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 3a03e53c1c9..1e74d449831 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -517,10 +517,6 @@ impl DerefMut for UniqueArc { unsafe { &mut *val } } } - -// SAFETY: `UniqueArc` has the same aliasing and synchronization properties -// as `&mut T`, and so it is `Sync` if `&mut T` is `Sync`. -unsafe impl Sync for UniqueArc where for<'a> &'a mut T: Sync {} ``` # Reference-level explanation From 6852c4c5a2f2432ed9f1a966ffdeb0b66e8456b7 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 15 Apr 2025 12:24:26 +0000 Subject: [PATCH 23/43] RFC3458: Make `Unsafe::new` safe See https://github.com/rust-lang/rfcs/pull/3458#discussion_r2040345750 --- text/0000-unsafe-fields.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 1e74d449831..f9c9b4bc435 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -766,7 +766,7 @@ pub struct Unsafe(T); impl Unsafe { -pub unsafe fn new(val: T) -> Self + pub fn new(val: T) -> Self where T: Sized { @@ -791,10 +791,9 @@ pub unsafe fn new(val: T) -> Self ``` However, this falls short of the assurances provided by first-class support for field safety. -`Unsafe::new` inherits the safety conditions of the field that the `Unsafe` will be assigned to, and -the safety conditions of its accessors inherit the safety conditions of the field that the `Unsafe` +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 are a product of the dataflow of the program. +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 From f9fd91b23d8ade413c6c4dfd0af225cc1227a6a6 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 15 Apr 2025 13:36:59 +0000 Subject: [PATCH 24/43] RFC3458: Document 'Syntactic Knobs and Wrapper Types' as future possibility --- text/0000-unsafe-fields.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index f9c9b4bc435..3c8ff0681bb 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -951,6 +951,15 @@ consensus, once that consensus is reached. # Future possibilities +## Syntactic Knobs and Wrapper Types + +This RFC is forwards-compatible with some future additions of [syntactic +knobs](#more-syntactic-granularity) and tooling-aware [wrapper types](#unsafe-wrapper-type). A +variant of `unsafe` that permits safe `&`-referencing could be introduced at any time without +breaking existing code. Over an edition boundary, safe reads of `unsafe` fields could be permitted +by rewriting existing `unsafe` fields to wrap their field type in a tooling-aware `Unsafe` wrapper +type. + ## Safe Unions Today, unions provide language support for fields with subtractive *language* invariants. Unions may From 6ebe8a2e36a57a7c6a6fb89899aa1283a1218493 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 14:57:56 +0000 Subject: [PATCH 25/43] RFC3458: Clarify that syntactic knob possibility is not an easy migration. --- text/0000-unsafe-fields.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 3c8ff0681bb..c030f5bcef8 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -953,12 +953,14 @@ consensus, once that consensus is reached. ## Syntactic Knobs and Wrapper Types -This RFC is forwards-compatible with some future additions of [syntactic -knobs](#more-syntactic-granularity) and tooling-aware [wrapper types](#unsafe-wrapper-type). A -variant of `unsafe` that permits safe `&`-referencing could be introduced at any time without -breaking existing code. Over an edition boundary, safe reads of `unsafe` fields could be permitted -by rewriting existing `unsafe` fields to wrap their field type in a tooling-aware `Unsafe` wrapper -type. +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. This RFC is forwards-compatible with some future additions of +[syntactic knobs](#more-syntactic-granularity) and tooling-aware [wrapper +types](#unsafe-wrapper-type). A variant of `unsafe` that permits safe `&`-referencing could be +introduced at any time without breaking existing code. Over an edition boundary, safe reads of +`unsafe` fields could be permitted by rewriting existing `unsafe` fields to wrap their field type in +a tooling-aware `Unsafe` wrapper type. This migration would be sound, but not seamless, and could +not be embarked on lightly. ## Safe Unions From 217dda6c97fc75ff6677a6e4642efd7f7c18f8cd Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 15:10:38 +0000 Subject: [PATCH 26/43] RFC3458: Document trivial proof drawback's interaction with borrow checker. Also discuss future possible interaction with partial borrows. --- text/0000-unsafe-fields.md | 56 ++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index c030f5bcef8..f8a87cdef1d 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -897,36 +897,29 @@ code. # Drawbacks -## Alarm Fatigue - -Although the `unsafe` keyword gives Rust's safety hygiene tooling insight into whether a field -carries safety invariants, it does not give Rust deeper insight into the semantics of those -invariants. Consequently, Rust must err on the side caution, requiring `unsafe` for most uses of -unsafe field — including uses that the programmer can see are conceptually harmless. - -In these cases, Rust's safety hygiene tooling will suggest that the harmless operation is wrapped -in an `unsafe` block, and the programmer will either: - -- comply and provide a trivial safety proof, or -- opt out of Rust's field safety tooling by removing the `unsafe` modifier from their field. - -The former is a private annoyance; the latter is a rebuttal of Rust's safety hygiene conventions -and tooling. Such rebuttals are not unprecedented in the Rust ecosystem. Even among prominent -projects, it is not rare to find a conceptually unsafe function that is not marked unsafe. The -discovery of such functions by the broader Rust community has, occasionally, provoked controversy. +## 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 convention of using visibility to enforce -field safety invariants. - -This RFC does not, itself, attempt to address alarm fatigue. Instead, we propose a simple extension -to Rust's safety tooling that is, by virtue of its simplicity, particularly amenable to future -iterative refinement. We imagine empowering Rust to reason about safety invariants, either via -[type-directed analyses](#safe-reads-for-fields-with-local-non-suspended-invariants), or via -syntactic extensions. The design of these refinements will be guided by the valuable usage data -produced by implementing this RFC. +soundness nor security issue to continue to adhere to the current convention of using visibility to +enforce field safety invariants. # Prior art @@ -951,6 +944,17 @@ 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 From 7617a81a66f8fb616fd07d3e7b9c0019e071d83d Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 16:50:38 +0000 Subject: [PATCH 27/43] RFC3458: Document alt of `Mixing Syntactic Knobs with a Wrapper Type` --- text/0000-unsafe-fields.md | 44 ++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index f8a87cdef1d..90b8680ef86 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -895,6 +895,36 @@ marked `unsafe` is unsafe to use* is both pedagogically simple and fail safe; i. 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 `&mut`-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 @@ -958,13 +988,13 @@ be invoked. Future language extensions that permit partial borrows will resolve ## 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. This RFC is forwards-compatible with some future additions of -[syntactic knobs](#more-syntactic-granularity) and tooling-aware [wrapper -types](#unsafe-wrapper-type). A variant of `unsafe` that permits safe `&`-referencing could be -introduced at any time without breaking existing code. Over an edition boundary, safe reads of -`unsafe` fields could be permitted by rewriting existing `unsafe` fields to wrap their field type in -a tooling-aware `Unsafe` wrapper type. This migration would be sound, but not seamless, and could -not be embarked on lightly. +space, it is not a one-way door. This RFC is 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). A variant of `unsafe` +that permits safe `&`-referencing could be introduced at any time without breaking existing code. +Over an edition boundary, safe reads of `unsafe` fields could be permitted by rewriting existing +`unsafe` fields to wrap their field type in a tooling-aware `Unsafe` wrapper type. This migration +would be sound, but not seamless, and could not be embarked on lightly. ## Safe Unions From 350ea738a17c0ace4d801d7ec342231a33cd0082 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 17:02:26 +0000 Subject: [PATCH 28/43] RFC3458: Add links to RFC PR and Rust tracking issue --- text/0000-unsafe-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 90b8680ef86..ba9555e1cd5 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -1,7 +1,7 @@ - Feature Name: `unsafe_fields` - Start Date: 2023-07-13 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +- 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 From 1ec4c2dc9abd92fc24be795d62bbd6287df4778d Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 17:17:44 +0000 Subject: [PATCH 29/43] RFC3458: Typo fix Resolves https://github.com/rust-lang/rfcs/pull/3458#discussion_r2050878878 --- text/0000-unsafe-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index ba9555e1cd5..47324c8cb89 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -904,7 +904,7 @@ along with two field safety modifiers: - `unsafe` All uses except reading are `unsafe`. - `unsafe(mut)` - All uses except reading and `&mut`-referencing are `unsafe`. + 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. From 49e8e740a06e3eafc0a983d8773215035aaad371 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 29 May 2025 16:17:14 +0000 Subject: [PATCH 30/43] RFC3458: Clarify 'Syntactic Knobs and Wrapper Types' possibility See: - https://github.com/rust-lang/rfcs/pull/3458#issuecomment-2917140820 - https://github.com/rust-lang/rfcs/pull/3458#issuecomment-2917392265 --- text/0000-unsafe-fields.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index 47324c8cb89..f40d5c2b0c4 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -988,13 +988,12 @@ be invoked. Future language extensions that permit partial borrows will resolve ## 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. This RFC is forwards-compatible with some future additions of some +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). A variant of `unsafe` -that permits safe `&`-referencing could be introduced at any time without breaking existing code. -Over an edition boundary, safe reads of `unsafe` fields could be permitted by rewriting existing -`unsafe` fields to wrap their field type in a tooling-aware `Unsafe` wrapper type. This migration -would be sound, but not seamless, and could not be embarked on lightly. +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()` +(e.g., `unsafe(mut)`) could be added to denote that some subset of uses is always safe. ## Safe Unions From 78dc9add33b06cd142e46cdb97e06d562a847079 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 2 Jan 2026 16:12:46 +0000 Subject: [PATCH 31/43] RFC3458: Clarify semantics in Reference-level explanation Addresses https://github.com/rust-lang/rfcs/pull/3458#discussion_r2628488907 --- text/0000-unsafe-fields.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/0000-unsafe-fields.md b/text/0000-unsafe-fields.md index f40d5c2b0c4..7c82e4ef565 100644 --- a/text/0000-unsafe-fields.md +++ b/text/0000-unsafe-fields.md @@ -539,6 +539,12 @@ StructField : The use of unsafe fields on unions shall remain forbidden while the [impact of this feature on unions](#safe-unions) is decided. +## Semantics + +Projections of fields marked `unsafe` must occur within the context of `unsafe`. + +Clippy's [`missing_safety_doc`] lint ensures such fields have accompanying safety documentation. + # Rationale and Alternatives The design of this proposal is primarily guided by three tenets: From b3639de7e9d42890a6dec11da98d257e24752582 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 2 Jan 2026 16:15:07 +0000 Subject: [PATCH 32/43] RFC3458: Move RFC to `3458-unsafe-fields.md` --- text/{0000-unsafe-fields.md => 3458-unsafe-fields.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{0000-unsafe-fields.md => 3458-unsafe-fields.md} (100%) diff --git a/text/0000-unsafe-fields.md b/text/3458-unsafe-fields.md similarity index 100% rename from text/0000-unsafe-fields.md rename to text/3458-unsafe-fields.md From c491b4998376671a6d6c4dc7b5ac8df7e0df7abe Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 17:46:57 +0000 Subject: [PATCH 33/43] Clean up whitespace --- text/3458-unsafe-fields.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index 7c82e4ef565..9f394d4c02a 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -12,7 +12,7 @@ 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>, @@ -77,7 +77,7 @@ construction and access through `unsafe` functions; e.g.: 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>, @@ -159,7 +159,7 @@ and where they are discharged. To denote that a field carries a library safety i ```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>, @@ -549,13 +549,13 @@ Clippy's [`missing_safety_doc`] lint ensures such fields have accompanying safet The design of this proposal is primarily guided by three tenets: -1. [**Unsafe Fields Denote Safety Invariants**](#tenet-unsafe-fields-denote-safety-invariants) +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) +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) +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. @@ -653,7 +653,7 @@ safe to use `arc` — it is not. By requiring `unsafe` to read `arc`, Rust's saf 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` (whose methods necessarily provide only general guidance). -Consequently, we require that moving unsafe fields out of their enclosing type requires `unsafe`. +Consequently, we require that moving unsafe fields out of their enclosing type requires `unsafe`. ### Field Copying is Safe From 21a659bfe9abc512e58009eae4b29ceaf4d5189c Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:07:07 +0000 Subject: [PATCH 34/43] Fix typos in function name and struct name Fix `as_mut_lug` to `as_mut_log` in example code (appears in error message output and diff example). Fix `BoxErased` to `BoxedErased` to match the struct definition. --- text/3458-unsafe-fields.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index 9f394d4c02a..0d3cebb65fe 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -239,7 +239,7 @@ error[E0133]: use of unsafe field is unsafe and requires unsafe block 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 { +24 | pub unsafe fn as_mut_log(&mut self) -> &mut u8 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: the lint level is defined here --> src/lib.rs:1:38 @@ -279,7 +279,7 @@ For more information about this error, try `rustc --explain E0133`. /// # 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 { + pub unsafe fn as_mut_log(&mut self) -> &mut u8 { - &mut self.pow + // SAFETY: The caller promises not to violate `pow`'s invariant. + unsafe { &mut self.pow } @@ -329,7 +329,7 @@ impl BoxedErased { ``` ...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 +undefined behavior: namely, if `BoxedErased` 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 From 1cad34605476685692ed40c5718188cc855865f4 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:08:13 +0000 Subject: [PATCH 35/43] Fix whitespace and clarify prose in example Remove extra space in "invariants to its callers". Simplify the sentence introducing the `BoxedErased` example by removing the confusing clause "that violates this invariant". The point is that merely adding the `unsafe` modifier does not prevent UB when the destructor has requirements. Fix missing parentheses in `TypeId::of::()` function call. --- text/3458-unsafe-fields.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index 0d3cebb65fe..c1d3a94dc2f 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -115,7 +115,7 @@ discharged entirely prior to entering the `unsafe` block, the surrounding functi 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 +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. @@ -302,8 +302,8 @@ by the library safety invariant that it contains valid UTF-8, but because it is 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.: +and alignment that the referent was allocated with. Merely adding the `unsafe` modifier to a `Box` +field, e.g.: ```rust struct BoxedErased { @@ -316,7 +316,7 @@ struct BoxedErased { impl BoxedErased { fn new(src: Box) -> Self { let data = …; // cast `Box` to `Box<[MaybeUninit]>` - let type_id = TypeId::of::; + let type_id = TypeId::of::(); // SAFETY: … unsafe { BoxedErased { @@ -329,8 +329,7 @@ impl BoxedErased { ``` ...does not ensure that using `BoxedErased` or its `data` field in safe contexts cannot lead to -undefined behavior: namely, if `BoxedErased` or its `data` field is dropped, its destructor may induce -UB. +undefined behavior: namely, if `BoxedErased` 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.: From 0857d93cae7a9d7dc75153e1a202c0f7896f22bc Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:08:43 +0000 Subject: [PATCH 36/43] Fix grammar issues Fix "relaxed variant" to "relaxed invariant" and add missing "is" in the sentence discussing the rarity of destructor-violating fields. Add missing word "field" in "an unsafe field is trivially sound to read". Fix "an union" to "a union" ("union" starts with consonant sound). --- text/3458-unsafe-fields.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index c1d3a94dc2f..76dcbf40323 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -701,7 +701,7 @@ Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe). We resolve this 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 +relaxed invariant, which is 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` @@ -844,7 +844,7 @@ These challenges motivate first-class support for field safety tooling. 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: +trivial safety comments. For example, in some scenarios, an unsafe field is trivially sound to read: ```rust struct Even { @@ -1004,7 +1004,7 @@ addition to this RFC's `unsafe` modifier, additional variants in the form `unsaf 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 +possible to place a 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)): From 1e3a4bfd724476640f7057639d03298030d4c920 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:09:39 +0000 Subject: [PATCH 37/43] Fix additional grammar and consistency issues Fix "by the Rust itself" to "by Rust itself". Fix doc comment reference from `BoxErased::data` to `BoxedErased::data` for consistency with struct name. Fix redundant "imposed by the ... imposes" to "the ... imposes". Fix "as consequence a copy" to "as a consequence of a copy". Fix "this this shape" to "of this shape". --- text/3458-unsafe-fields.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index 76dcbf40323..fd254af4287 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -145,7 +145,7 @@ 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 +true or else undefined behavior may arise. Language safety invariants are imposed by 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 @@ -338,7 +338,7 @@ field in `ManuallyDrop`; e.g.: struct BoxedErased { /// SAFETY: `data`'s logical type has `type_id`. - unsafe data: Box<[MaybeUninit]>, - /// SAFETY: See [`BoxErased::data`]. + /// SAFETY: See [`BoxedErased::data`]. + unsafe data: ManuallyDrop]>>, unsafe type_id: TypeId, } @@ -578,8 +578,8 @@ conditional on upholding safety invariants; for example: > 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: +We adopt this tenet because it is consistent with the requirements 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. @@ -658,7 +658,7 @@ Consequently, we require that moving unsafe fields out of their enclosing type r 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` that +violated as a consequence of a copy. For example, consider a field of type `&'static RefCell` 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`. @@ -710,7 +710,7 @@ fields have trivial destructors, à la union fields, by requiring that `unsafe` 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: +provides an API of this shape: ```rust pub struct SafeAbstraction { From 0db5073273654593a03115b23da04c210f4cbdb0 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:27:01 +0000 Subject: [PATCH 38/43] Fix hyphenation per AE conventions Change "non-trivial" to "nontrivial". In American English, "nontrivial" is written as a single unhyphenated word. Change "forwards-compatible" to "forward-compatible". American English prefers "forward" over "forwards" as the adjective form. --- text/3458-unsafe-fields.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index fd254af4287..94e38e5a4b4 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -301,7 +301,7 @@ struct MaybeInvalidStr { 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 +By contrast, `Box` has a nontrivial destructor which requires that its referent has the same size and alignment that the referent was allocated with. Merely adding the `unsafe` modifier to a `Box` field, e.g.: @@ -692,9 +692,9 @@ However, the `ptr` field introduces a declaration-site safety obligation that is 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 +### Nontrivial Destructors are Prohibited -If a programmer applies the `unsafe` modifier to a field with a non-trivial destructor and relaxes +If a programmer applies the `unsafe` modifier to a field with a nontrivial 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 @@ -994,7 +994,7 @@ be invoked. Future language extensions that permit partial borrows will resolve 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 +an edition boundary. This RFC is also forward-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()` From 64930d9750356a55facc86269b9846265b64086f Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:31:48 +0000 Subject: [PATCH 39/43] Use "that" for restrictive clauses It's preferable to use "that" for restrictive (defining / essential) clauses and "which" for nonrestrictive (parenthetical / nonessential) clauses. --- text/3458-unsafe-fields.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index 94e38e5a4b4..05917ccc477 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -37,7 +37,7 @@ 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 +that carries the invariants that it is less than the capacity of the `Vec` and that all elements in the `Vec` 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. @@ -166,7 +166,7 @@ pub struct UnalignedRef<'a, T> { } ``` -You should use the `unsafe` keyword on any field that carries a library safety invariant which +You should use the `unsafe` keyword on any field that carries a library safety invariant that differs from the invariant provided by its type. The `unsafe` field modifier is only applicable to named fields. You should avoid attaching library @@ -301,7 +301,7 @@ struct MaybeInvalidStr { 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 nontrivial destructor which requires that its referent has the same size +By contrast, `Box` has a nontrivial destructor that requires that its referent has the same size and alignment that the referent was allocated with. Merely adding the `unsafe` modifier to a `Box` field, e.g.: @@ -552,10 +552,10 @@ The design of this proposal is primarily guided by three tenets: 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 + Uses of `unsafe` fields that 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 + Uses of `unsafe` fields that 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 — @@ -570,12 +570,12 @@ We adopt this tenet because it is consistent with the purpose of the `unsafe` ke 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. +- An `unsafe` trait denotes that it carries safety invariants that must be upheld by implementors. +- An `unsafe` function denotes that it carries safety invariants that 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 +> Uses of `unsafe` fields that could violate their invariants *must* occur in the scope of an > `unsafe` block. We adopt this tenet because it is consistent with the requirements the `unsafe` keyword @@ -586,7 +586,7 @@ imposes when applied to other declarations; for example: ## Tenet: Safe Usage is Usually Safe -> Uses of `unsafe` fields which cannot violate their invariants *should not* require an unsafe block. +> Uses of `unsafe` fields that 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 @@ -695,7 +695,7 @@ Unsafe**](#tenet-unsafe-usage-is-always-unsafe). ### Nontrivial Destructors are Prohibited If a programmer applies the `unsafe` modifier to a field with a nontrivial destructor and relaxes -its invariant beyond that which is required by the field's destructor, Rust cannot prevent the +its invariant beyond that 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 From 3e26be760b947a6bc0c9f854bf9a10c8ffc257d1 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:32:15 +0000 Subject: [PATCH 40/43] Replace "upon" with "on" When "on" can be used instead of "upon", it generally should be. --- text/3458-unsafe-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index 05917ccc477..3bc79fdf608 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -132,7 +132,7 @@ 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 +code. If `unsafe` code depends on 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*].) @@ -370,7 +370,7 @@ impl Zeroed { ### 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, +in undefined behavior. In the below example, unsafe code may rely on `alignment_pow`s invariant, but not `size`'s invariant: ```rust From 45c3c557f0f3e1aaa6753b84dca2bf1910718ef1 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:32:42 +0000 Subject: [PATCH 41/43] Replace "presently" with "currently" The word "presently" is ambiguous: it can mean "soon" or "currently". Let's use "currently" when that is the intended meaning to eliminate ambiguity. --- text/3458-unsafe-fields.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index 3bc79fdf608..a2d0d96789e 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -130,9 +130,9 @@ anticipate that tooling support for field safety hygiene will encourage programm 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* +Currently, 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 on field safety invariants, those invariants may presently be +code. If `unsafe` code depends on field safety invariants, those invariants can currently 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*].) @@ -709,7 +709,7 @@ fields have trivial destructors, à la union fields, by requiring that `unsafe` `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 +place library authors in an impossible dilemma. To illustrate, let's say a library author currently provides an API of this shape: ```rust @@ -720,7 +720,7 @@ pub struct SafeAbstraction { } ``` -...and a downstream user presently consumes this API like so: +...and a downstream user currently consumes this API like so: ```rust let val = SafeAbstraction::default(); From 816d4e7702118fd89f36264202ee639df37c97b5 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 18:33:03 +0000 Subject: [PATCH 42/43] Fix miscellaneous grammar issues - Fix spelling of "accommodate". - Fix missing "of" in "the benefits doing so" -> "the benefits of doing so". - Use "failsafe" (one word) when used as an adjective. --- text/3458-unsafe-fields.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index a2d0d96789e..3115ee401f8 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -380,7 +380,7 @@ struct Layout { /// # 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. + /// To accommodate incomplete layouts (i.e., those missing trailing padding), this is not a safety invariant. pub size: usize, /// The log₂(alignment) of a type. /// @@ -558,7 +558,7 @@ The design of this proposal is primarily guided by three tenets: Uses of `unsafe` fields that 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 — +This RFC prioritizes the first two tenets before the third. We believe that the benefits of 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. @@ -896,7 +896,7 @@ user internalize this hierarchy, or else risk selecting the wrong modifier for t 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 +marked `unsafe` is unsafe to use* is both pedagogically simple and failsafe; 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. From f5749baed7b7ecfe03d95dc9197d0c1344712f43 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 27 Jan 2026 21:00:33 +0000 Subject: [PATCH 43/43] Fix heading levels We use h2 as the top heading level in RFCs; also some headers jumped a level in the document. --- text/3458-unsafe-fields.md | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/text/3458-unsafe-fields.md b/text/3458-unsafe-fields.md index 3115ee401f8..4d9bc5b975d 100644 --- a/text/3458-unsafe-fields.md +++ b/text/3458-unsafe-fields.md @@ -3,7 +3,7 @@ - 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 +## 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 @@ -25,7 +25,7 @@ accompanying safety documentation. [`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc -# Motivation +## 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, @@ -142,7 +142,7 @@ consider distant safe code. (See [*The Scope of Unsafe*].) 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 +## 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 Rust @@ -344,9 +344,9 @@ field in `ManuallyDrop`; e.g.: } ``` -## When *Not* To Use Unsafe Fields +### When *Not* To Use Unsafe Fields -### Relaxing a Language Invariant +#### 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 @@ -367,7 +367,7 @@ impl Zeroed { ...because `Zeroed::::zeroed()` induces undefined behavior. -### Denoting a Correctness Invariant +#### 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 on `alignment_pow`s invariant, @@ -398,7 +398,7 @@ We might also imagine a variant of the above example where `alignment_pow`, like 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 +### Complete Example The below example demonstrates how field safety support can be applied to build a practical abstraction with small safety boundaries @@ -518,9 +518,9 @@ impl DerefMut for UniqueArc { } ``` -# Reference-level explanation +## Reference-level explanation -## Syntax +### 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`: @@ -538,13 +538,13 @@ StructField : The use of unsafe fields on unions shall remain forbidden while the [impact of this feature on unions](#safe-unions) is decided. -## Semantics +### Semantics Projections of fields marked `unsafe` must occur within the context of `unsafe`. Clippy's [`missing_safety_doc`] lint ensures such fields have accompanying safety documentation. -# Rationale and Alternatives +## Rationale and Alternatives The design of this proposal is primarily guided by three tenets: @@ -562,7 +562,7 @@ This RFC prioritizes the first two tenets before the third. We believe that the 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 +### Tenet: Unsafe Fields Denote Safety Invariants > A field *should* be marked `unsafe` if it carries library safety invariants. @@ -573,7 +573,7 @@ conditional on upholding safety invariants; for example: - An `unsafe` trait denotes that it carries safety invariants that must be upheld by implementors. - An `unsafe` function denotes that it carries safety invariants that must be upheld by callers. -## Tenet: Unsafe Usage is Always Unsafe +### Tenet: Unsafe Usage is Always Unsafe > Uses of `unsafe` fields that could violate their invariants *must* occur in the scope of an > `unsafe` block. @@ -584,7 +584,7 @@ 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 +### Tenet: Safe Usage is Usually Safe > Uses of `unsafe` fields that cannot violate their invariants *should not* require an unsafe block. @@ -593,12 +593,12 @@ experience of practicing it. We adopt this tenet as a forcing function between d our first two tenets. All else being equal, we give priority to designs that minimize the needless use of `unsafe`. -## Alternatives +### 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 +#### 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.: @@ -629,7 +629,7 @@ variants are conceptually unsafe, requiring the programmer to use `unsafe` even of 'safe' fields. This violates [*Tenet: Safe Usage is Usually Safe*](#tenet-safe-usage-is-usually-safe). -### Field Moving is 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 @@ -654,7 +654,7 @@ documents its safety conditions as they relate to `KeepAlive`), rather than in d interactions with `UnsafeCell` (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 +#### 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 @@ -663,7 +663,7 @@ imposes an invariant on the value of `T`. In this alternative proposal, such a f 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 +#### 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 @@ -692,7 +692,7 @@ However, the `ptr` field introduces a declaration-site safety obligation that is with `unsafe` at any use site; this violates [**Tenet: Unsafe Usage is Always Unsafe**](#tenet-unsafe-usage-is-always-unsafe). -### Nontrivial Destructors are Prohibited +#### Nontrivial Destructors are Prohibited If a programmer applies the `unsafe` modifier to a field with a nontrivial destructor and relaxes its invariant beyond that required by the field's destructor, Rust cannot prevent the @@ -758,7 +758,7 @@ Unsafe Fields Denote Safety Invariants**](#tenet-unsafe-fields-denote-safety-inv requiring trivially `unsafe` drop glue), a violation of [**Tenet: Safe Usage is Usually Safe**](#tenet-safe-usage-is-usually-safe). -### Unsafe Wrapper Type +#### 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 @@ -840,7 +840,7 @@ exchanging the `len`s of two instances of the aforementioned `Vec`). These challenges motivate first-class support for field safety tooling. -### More Syntactic Granularity +#### 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 @@ -900,7 +900,7 @@ marked `unsafe` is unsafe to use* is both pedagogically simple and failsafe; i.e 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 +#### 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, @@ -930,9 +930,9 @@ 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 +## Drawbacks -## Trivial Safety Proofs +### 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 @@ -956,19 +956,19 @@ but **not** *must* — denote field safety invariants with the `unsafe` keyword. soundness nor security issue to continue to adhere to the current convention of using visibility to enforce field safety invariants. -# Prior art +## 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 +## 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 +### 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 @@ -977,9 +977,9 @@ debated](https://github.com/rust-lang/unsafe-code-guidelines/issues/539). This R prescribe its terminology. Documentation of the unsafe fields tooling should reflect broader consensus, once that consensus is reached. -# Future possibilities +## Future possibilities -## Partial Borrows +### 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 @@ -990,7 +990,7 @@ maintainer can define a *safe* accessor (i.e., 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 +### 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 @@ -1000,7 +1000,7 @@ knobs](#more-syntactic-granularity) and [wrapper types](#unsafe-wrapper-type). F addition to this RFC's `unsafe` modifier, additional variants in the form `unsafe()` (e.g., `unsafe(mut)`) could be added to denote that some subset of uses is always safe. -## Safe Unions +### 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