From 95dd2953348cf8d4e206a7086c045b6423fa3312 Mon Sep 17 00:00:00 2001 From: Jacob Pratt Date: Thu, 13 Jul 2023 00:38:58 -0400 Subject: [PATCH 01/29] 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 fa712be304db0bd4af2f90d6473cc778bb1bff21 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 5 Jan 2025 12:30:43 -0500 Subject: [PATCH 02/29] 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 46766ed1dbe07155bb7241db34f4563b9b0157d6 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 5 Jan 2025 14:12:34 -0500 Subject: [PATCH 03/29] 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 bb6e76ac19634aaa3266692cdf14a5d102f9d651 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sun, 5 Jan 2025 16:10:38 -0500 Subject: [PATCH 04/29] 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 aafd5f4720a53962da520486f264a652c076c77c Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Mon, 6 Jan 2025 12:47:22 -0500 Subject: [PATCH 05/29] 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 ed5ceb4b9fc78113012cbdbf06ae50dbb3a35a69 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 8 Jan 2025 17:34:35 +0000 Subject: [PATCH 06/29] 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 3f851ae815da2a3a1971f4aa09752805596f74ae Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 8 Jan 2025 19:31:34 +0000 Subject: [PATCH 07/29] 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 990841938371f48184c8024e350b89f6fef747dc Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 9 Jan 2025 15:23:49 +0000 Subject: [PATCH 08/29] 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 247bc411b75d9e9ce59357ce5e16bc4d019d868b Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 9 Jan 2025 20:07:41 +0000 Subject: [PATCH 09/29] 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 2d7a5121f21ab1039051b95f991d7671e17b74b1 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 25 Feb 2025 18:56:23 +0000 Subject: [PATCH 10/29] 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 000075f284cb30ed415f211e42e1fc6365ae763c Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 25 Feb 2025 18:57:55 +0000 Subject: [PATCH 11/29] 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 1d0d8be75f89821dcdcdc9878a42863712aa82bd Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 26 Feb 2025 18:07:27 +0000 Subject: [PATCH 12/29] 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 e8aebe2e72106d539e87cc695bb6af8da729afe4 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 26 Feb 2025 18:37:11 +0000 Subject: [PATCH 13/29] 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 9521d122b3d0edfe09320b3d56679897c9d8298f Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 26 Feb 2025 19:28:46 +0000 Subject: [PATCH 14/29] 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 27628e04a7dab2113fce34f900b0757008a85bf2 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 27 Feb 2025 18:41:30 +0000 Subject: [PATCH 15/29] 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 f04701b5909973f9f7705ab44713e7b932e4f49f Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 28 Feb 2025 19:09:26 +0000 Subject: [PATCH 16/29] 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 ed920e9975023cde7244d2947c7adecdc6b9a4a2 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 2 Apr 2025 18:17:34 +0000 Subject: [PATCH 17/29] 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 cf0516b811f537e9b19d49f9797ecd6efc963584 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 2 Apr 2025 20:25:55 +0000 Subject: [PATCH 18/29] 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 aa0c3339681803c5b36bc18770cf9f3193476740 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 11 Apr 2025 17:50:28 +0000 Subject: [PATCH 19/29] 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 322c4a871f6ad0a272ef5151b702cd9a1932864c Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 11 Apr 2025 19:24:52 +0000 Subject: [PATCH 20/29] 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 a5cc19f445dd9b18d6d8061cf932d4aa64302cec Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 11 Apr 2025 19:33:10 +0000 Subject: [PATCH 21/29] 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 b229496259978deaf7c2956e4b0ff1f6bc16aa95 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 15 Apr 2025 12:22:14 +0000 Subject: [PATCH 22/29] 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 f4426ea685768ae40d7a80a3ec9ceb5f91fc64bc Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 15 Apr 2025 12:24:26 +0000 Subject: [PATCH 23/29] 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 f4e85fb15558dfa32e0998880ea2238461386f88 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 15 Apr 2025 13:36:59 +0000 Subject: [PATCH 24/29] 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 94c173a653ed1f86d0ee139ad790c56b59765f4b Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 14:57:56 +0000 Subject: [PATCH 25/29] 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 462cee49e8ffe45fc368adcc23e1d0c5512075bc Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 15:10:38 +0000 Subject: [PATCH 26/29] 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 d1d73886080074c9482a25022d30946c8a852259 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 16:50:38 +0000 Subject: [PATCH 27/29] 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 f5a82f8d980becd72bdc449b8beac49b73aa1f95 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 17:02:26 +0000 Subject: [PATCH 28/29] 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 d4cfd149f730c71b410885f16e3ccdf2bdd0d64e Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 18 Apr 2025 17:17:44 +0000 Subject: [PATCH 29/29] 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.