Skip to content
Open
Changes from 24 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
af62890
Stabilization RFC for `core::marker::Freeze` in bounds
p-avital May 10, 2024
106bc46
Make this a proper RFC
p-avital May 13, 2024
902b79d
Add line wraps for legibility.
p-avital May 13, 2024
126f8f0
Update text/0000-stabilize-marker-freeze.md
p-avital May 13, 2024
94ef594
Update 0000-stabilize-marker-freeze.md
p-avital May 14, 2024
34b9775
Update text/0000-stabilize-marker-freeze.md
p-avital May 14, 2024
3a104b1
Update text/0000-stabilize-marker-freeze.md
p-avital May 14, 2024
6e15d06
Update text/0000-stabilize-marker-freeze.md
p-avital May 14, 2024
c5b3fe8
Update text/0000-stabilize-marker-freeze.md
p-avital May 14, 2024
2b4f996
Update text/0000-stabilize-marker-freeze.md
p-avital May 14, 2024
33ffcc6
Update text/0000-stabilize-marker-freeze.md
p-avital May 14, 2024
b339b0d
Address a batch of comments with actionnable suggestions
p-avital May 22, 2024
30a03fc
Update remaining questions
p-avital May 22, 2024
c8fa805
Propose Freeze->ShallowImmutable and PhantomNotFreeze
p-avital May 22, 2024
492a594
Update text/0000-stabilize-marker-freeze.md
p-avital May 22, 2024
c01e96c
Update 0000-stabilize-marker-freeze.md
p-avital May 22, 2024
405b322
Add motivation for the trait renaming
p-avital May 25, 2024
14abf77
Update text/0000-stabilize-marker-freeze.md
p-avital May 26, 2024
c1fedd5
Update text/0000-stabilize-marker-freeze.md
p-avital May 31, 2024
04e39d4
Update RFC following the 2024-07-24 design meeting
p-avital Jul 27, 2024
c7eab79
Apply suggestions from code review
p-avital Jul 28, 2024
84c1aad
Update RFC after design meeting 2025-03-19
p-avital Mar 19, 2025
65f1c90
Update summary per 2025-03-19 lang design meeting
traviscross Mar 19, 2025
b5c089b
Clean up whitespace
traviscross Mar 19, 2025
9ac85ff
Update 0000-stabilize-marker-freeze.md
p-avital Mar 19, 2025
7d91686
Remove the open questions
traviscross Mar 19, 2025
ebffacf
Update the RFC metadata and rename the file
traviscross Mar 19, 2025
ad10530
Merge branch 'rust-lang:master' into stabilize-marker-freeze
p-avital Mar 19, 2025
4225d5c
Update 3633-freeze-in-bounds.md
p-avital Mar 19, 2025
a274335
Rename `Freeze` to `NoCell`
p-avital Mar 27, 2025
4172280
Apply suggestions from code review
p-avital Apr 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions text/0000-stabilize-marker-freeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
- Feature Name: `stabilize_marker_freeze`
- Start Date: 2024-05-10
- 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
[summary]: #summary

- Stabilize `core::marker::Freeze` in trait bounds.
- Provide a `PhantomNotFreeze` marker type to opt out of `Freeze`.
- Change `PhantomData<T>` to implement `Freeze` only if `T: Freeze`.

# Motivation
[motivation]: #motivation

With 1.78, Rust [changed behavior](https://github.com/rust-lang/rust/issues/121250): previously, `const REF: &T = &expr;` was (accidentally) accepted even when `expr` may contain interior mutability.
Now this requires that the type of `expr` satisfies `T: core::marker::Freeze`, which indicates that `T` doesn't contain any un-indirected `UnsafeCell`, meaning that `T`'s memory cannot be modified through a shared reference.

The purpose of this change was to ensure that interior mutability cannot affect content that may have been static-promoted in read-only memory, which would be a soundness issue.
However, this new requirement also prevents using static-promotion to create constant references to data of generic type. This pattern can be used to approximate "generic `static`s" (with the distinction that static-promotion doesn't guarantee a unique address for the promoted content). An example of this pattern can be found in `stabby` and `equator`'s shared way of constructing v-tables:
```rust
pub trait VTable<'a>: Copy {
const VT: &'a Self;
}
pub struct VtAccumulator<Tail, Head> {
tail: Tail,
head: Head,
}
impl<Tail: VTable<'a>, Head: VTable<'a>> VTable<'a> for VtAccumulator<Tail, Head> {
const VT: &'a Self = &Self {tail: *Tail::VT, head: *Head::VT}; // Doesn't compile since 1.78
}
```

Making `VTable` a subtrait of `core::marker::Freeze` in this example is sufficient to allow this example to compile again, as static-promotion becomes legal again. This is however impossible as of today due to `core::marker::Freeze` being restricted to `nightly`.

Orthogonally to static-promotion, `core::marker::Freeze` can also be used to ensure that transmuting `&T` to a reference to an interior-immutable type (such as `[u8; core::mem::size_of::<T>()]`) is sound (as interior-mutation of a `&T` may lead to UB in code using the transmuted reference, as it expects that reference's pointee to never change). This is notably a safety requirement for `zerocopy` and `bytemuck` which are currently evaluating the use of `core::marker::Freeze` to ensure that requirement; or rolling out their own equivalents (such as zerocopy's `Immutable`) which imposes great maintenance pressure on these crates to ensure they support as many types as possible. They could stand to benefit from `core::marker::Freeze`'s status as an auto-trait, and `zerocopy` intends to replace its bespoke trait with a re-export of `core::marker::Freeze`.

Note that for this latter use-case, `core::marker::Freeze` isn't entirely sufficient, as an additional proof that `T` doesn't contain padding bytes is necessary to allow this transmutation to be safe, as reading one of `T`'s padding bytes as a `u8` would be UB.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

`core::marker::Freeze` is a trait that is implemented for any type whose memory layout doesn't contain any `UnsafeCell`: it indicates that the memory referenced by `&T` is guaranteed not to change while the reference is live.

It is automatically implemented by the compiler for any type that doesn't contain an un-indirected `core::cell::UnsafeCell`.

Notably, a `const` can only store a reference to a value of type `T` if `T: core::marker::Freeze`, in a pattern named "static-promotion".

As `core::marker::Freeze` is an auto-trait, it poses an inherent semver-hazard (which is already exposed through static-promotion): this RFC proposes the simultaneous addition and stabilization of a `core::marker::PhantomNotFreeze` type to provide a stable mean for maintainers to reliably opt out of `Freeze` without forbidding zero-sized types that are currently `!Freeze` due to the conservativeness of `Freeze`'s implementation being locked into remaining `!Freeze`.

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

## `core::marker::Freeze`

The following documentation is lifted from the current nightly documentation.
```markdown
Used to determine whether a type contains
any `UnsafeCell` internally, but not through an indirection.
This affects, for example, whether a `static` of that type is
placed in read-only static memory or writable static memory.
This can be used to declare that a constant with a generic type
will not contain interior mutability, and subsequently allow
placing the constant behind references.
# Safety
This trait is a core part of the language, it is just expressed as a trait in libcore for
convenience. Do *not* implement it for other types.
```

From a cursory review, the following documentation improvements may be considered:

```markdown
[`Freeze`] marks all types that do not contain any un-indirected interior mutability.
This means that their byte representation cannot change as long as a reference to them exists.

Note that `T: Freeze` is a shallow property: `T` is still allowed to contain interior mutability,
provided that it is behind an indirection (such as `Box<UnsafeCell<U>>`).
Notable `!Freeze` types are [`UnsafeCell`](core::cell::UnsafeCell) and its safe wrappers
such as the types in the [`cell` module](core::cell), [`Mutex`](std::sync::Mutex), and [atomics](core::sync::atomic).
Any type which contains a non-`Freeze` type without indirection also does not implement `Freeze`.

`T: Freeze` is notably a requirement for static promotion (`const REF: &'a T;`) to be legal.

Note that static promotion doesn't guarantee a single address: if `REF` is assigned to multiple variables,
they may still refer to distinct addresses.

Whether or not `T` implements `Freeze` may also affect whether `static STATIC: T` is placed
in read-only static memory or writeable static memory, or the optimizations that may be performed
in code that holds an immutable reference to `T`.

# Semver hazard
`Freeze` being an auto-trait that encodes a low level property of the types it is implemented for,
it is strongly advised against to rely on external types maintaining that property unless that
contract is explicitly stated out-of-band (through documentation, for example).

Conversely, authors that consider `Freeze` to be part of a type's contract should document this
fact explicitly.

## The ZST caveat
While `UnsafeCell<T>` is currently `!Freeze` regardless of `T`, allowing `UnsafeCell<T>: Freeze` if `T` is
a Zero-Sized-Type is currently under consideration.

Therefore, the advised way to make your types `!Freeze` regardless of their actual contents is to add a
[`PhantomNotFreeze`](core::marker::PhantomNotFreeze) field to it.

[`PhantomData<T>`](core::marker::PhantomData) only implements `Freeze` if `T` does, making it a good way
to conditionally remove a generic type's `Freeze` auto-impl.
Copy link
Member

@RalfJung RalfJung Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently not true (PhantomData<T> is always Freeze). And when I tried changing that there was significant fallout, though I do not remember the details.

EDIT: Ah this is discussed later. That's confusing, the RFC should make it clear when it is stating things that are already true vs proposing changes to be made. :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably, this is an example of what the Freeze doc could/should look like once implemented :p


# Safety
This trait is a core part of the language, it is just expressed as a trait in libcore for
convenience. Do *not* implement it for other types.
```

Mention could be added to `UnsafeCell` and atomics that adding one to a previously `Freeze` type without an indirection (such as a `Box`) is a SemVer hazard, as it will revoke its implementation of `Freeze`.

## Fixing `core::marker::PhantomData`'s `Freeze` impl

At time of writing, `core::marker::PhantomData<T>` implements `Freeze` regardless of whether or not `T` does.

This is now considered a bug, with the corrected behaviour being that `core::marker::PhantomData<T>` only implements `Freeze` if `T` does.

While crates that would "observe" this change exist, the current consensus is that it would only break invalid usages of that invariable bound.
Most crates that would observe this change could replace their usage of `core::marker::PhantomData<T>` by `core::marker::PhantomData<Ptr<T>>` where `Ptr<T>`
is a pointer-type with the relevant caracteristics regarding lifetime, `Send` and `Sync`ness.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cases affected by this change seem like quite the edge-cases, and the fact that there are workarounds makes me personally not super worried about it from a breakage standpoint1. Also, AFAICT this change shouldn't lead to any miscompilations since it can only remove Freeze from types that are currently Freeze; so the "worst kind" of breakage is completely avoided.

The necessary workarounds for code affected by this change should probably be documented somewhere, though. Perhaps just the release notes of when this change happens.


As for the "necessary workarounds":

Box<T> works for "ptr with the same auto-traits as T except is Freeze"2 (though see below for Unpin), but note that in #![no_std] code without extern crate alloc;, there is no "pre-built" pointer type in core for this:

  • &mut T: invariant in T, might necessitate adding a lifetime parameter where there wasn't one previously(?),
  • &T: wrong Sendness (&T: Send when T: Sync), might necessitate adding a lifetime parameter where there wasn't one previously(?)
  • *mut T: invariant in T, wrong Send/Syncness (never Send or Sync)
  • *const T/NonNull<T>: wrong Send/Syncness (never Send or Sync)

However, users can use PhantomData<&T/*const T/NonNull<T>> and just manually impl Send/Sync3 as appropriate to fix the differences if they are really constrained and cannot even use extern crate alloc;.


For Unpin, all of these pointer types (including Box<T>) are unconditionally Unpin, so if users really need to keep the "MyType<T>: Unpin iff T: Unpin" behavior while also remaining Freeze, they'd need to also add a PhantomPinned field and an impl<T: Unpin> Unpin for MyType<T>4.

Footnotes

  1. I'll put any "is this the best choice of behavior" comments in a different thread

  2. I am completely ignoring UnwindSafe and RefUnwindSafe and have not checked them for anything in this comment.

  3. If they use PhantomData<&T>, then they only need to impl<T: Send> Send for MyType<T>4, as the Sync impl is already correct. If they use PhantomData<*const T/NonNull<T>>, they'd need to have manual impls for both Send and Sync to get back the original behavior in the general case, but wouldn't need to add a lifetime parameter.

  4. modified as necessary to account for other fields 2


The author doesn't have the necessary knowledge to implement this change, which should still be subject to a crater run to ensure no valid use-cases were missed.
Copy link
Member

@RalfJung RalfJung Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is trivial, see rust-lang/rust#114559. The hard part is dealing with all the ecosystem breakage.

EDIT: Looking at it again, it's not even that bad.


This behaviour change shall be introduced at the same time as the stabilization of `Freeze` in bounds.

## `core::marker::PhantomNotFreeze`

This ZST is proposed as a means for maintainers to reliably opt out of `Freeze` without constraining currently `!Freeze` ZSTs to remain so.

Leveraging the proposed changes to `core::marker::PhantomData`'s `Freeze` impl, its implementation could be as trivial as a newtype or type alias on `core::marker::PhantomData<core::cell::SyncUnsafeCell<u8>>`,
with the following documentation:

```markdown
[`PhantomNotFreeze`] is a type with the following guarantees:
- It is guaranteed not to affect the layout of a type containing it as a field.
- Any type including it in its fields (including nested fields) without indirection is guaranteed to be `!Freeze`.

This latter property is [`PhantomNotFreeze`]'s raison-d'être: while other Zero-Sized-Types may currently be `!Freeze`,
[`PhantomNotFreeze`] is the only ZST (outside of [`PhantomData<T>`] where `T` isn't `Freeze`) that is guaranteed to stay that way.

Notable types that are currently `!Freeze` but might not remain so in the future are:
- `UnsafeCell<T>` where `core::mem::size_of::<T>() == 0`
- `[T; 0]` where `T: !Freeze`.
```

This new marker type shall be introduced at the same time as the stabilization of `Freeze` in bounds.

## Addressing the naming

A point of contention during the RFC's discussions was whether `Freeze` should be renamed, as `freeze` is already a term used in `llvm` to refer to an intrinsic which allows to safely read from uninitialized memory.
[Another RFC](https://github.com/rust-lang/rfcs/pull/3605) is currently open to expose this intrinsic in Rust.

Debates have landed on the conservation of the `Freeze` name, under the main considerations that:
- No better name was found despite the efforts in trying to find one,
- that the current name was already part of the Rust jargon,
- and that stabilizing this feature too valuable to hold it back on naming.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to push back on this. Our experience in zerocopy has shown that Immutable (the name for our trait whose semantics are ~identical to Freeze) is a very appropriate name. We often end up writing safety comments/proofs which need to reference the concept of interior mutability, and Immutable has proven a very intuitive name for this. I'd suggest that Immutable would be a much better name than Freeze for stabilization.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proposition was made several times during the development of this RFC.

I'm strongly against "just" Immutable, as it straight up lies and could give false impressions at a glance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the sense in which it lies? That &mut T still permits mutation?

Copy link
Member

@jswrenn jswrenn Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also like to voice serious reservations about this decision:

  1. Freeze is definitely not part of the average Rust programmer's lexicon — it's compiler jargon.
  2. Freeze, as noted in the preceding paragraph, conflicts with an entirely unrelated concept.

Naming is typically finalized during stabilization, and I'm surprised that this feature — unlike all other library additions (afaik) — is considered so urgent as to warrant a fast-tracked stabilization that bypasses the usual naming discussions. Naming this trait is the one shot we get at picking what term should be in the average Rust programmer's lexicon, and I don't think we should discard that opportunity lightly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. One could expect that at first glance Immutable is somehow protected from mutation even if you have the sole reference to it (as people like to state that &uniq is a more "exact" name for what &mut truly is).

Too simple a name would give an illusion of simplicity which may turn into a trap. Some languages are built around the concept of immutability, so it's not far fetch that someone may find impl Immutable for X in the doc of a type and assume "its some immutable FP stuff or something"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood. IMO something that at least mentions immutability (e.g. SharedImmutable) would be preferable. I agree with @jswrenn that Freeze is an unintuitive name that most average programmers won't be familiar with, and it's confusing to have it conflated with the freeze operation.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Freeze is definitely not part of the average Rust programmer's lexicon — it's compiler jargon.

Nor will it really need to be, it's a rather niche feature; but people in that niche have become familiar with it for a long time.

Freeze, as noted in the preceding paragraph, conflicts with an entirely unrelated concept.

An unrelated concept which it doesn't describe any better, and which is just as niche (in the sense that one has to be quite familiar with LLVM's variable model to understand it at all), and could possibly be expressed better in the standard library through APIs on MaybeUninit that would rely on this intrinsic.

Also, there's no reason to believe this conflict is all that important: they are 2 concepts in different namespaces that happen to share a name. One is a method and the other a trait; both of which requiring enough homework to use properly that I wouldn't expect anyone with a need for them to mistake one for the other.

Naming is typically finalized during stabilization, and I'm surprised that this feature — unlike all other library additions (afaik) — is considered so urgent as to warrant a fast-tracked stabilization that bypasses the usual naming discussions.

Note that this is a stabilization PR, and that it's been up for long enough that I wouldn't quite call that "fast-tracked".

Naming this trait is the one shot we get at picking what term should be in the average Rust programmer's lexicon, and I don't think we should discard that opportunity lightly.

I don't think it was discarded "lightly": it has been the object of much discussion, including the 2 design meetings on the topic of stabilization. It was discarded because no better name was found for it.

Copy link
Member

@jswrenn jswrenn Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some languages are built around the concept of immutability, so it's not far fetch that someone may find impl Immutable for X in the doc of a type and assume "its some immutable FP stuff or something"

Note that Freeze carries this same pitfall. In Javascript, "freeze" is the operation that makes an object immutable, even if you're the sole owner of it.

Copy link
Member

@Nadrieril Nadrieril Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InteriorImmutable should be understandable by any non-beginner Rust programmer. PhantomNotInteriorImmutable is a mouthful but honestly I don't mind if we get into a habit of having PhantomNotX types for marker traits.

Copy link
Contributor

@traviscross traviscross Mar 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For one of the earlier discussions, see:

For the rationale from @p-avital's design meeting document today, see:

The most bikesheddy aspect of this RFC has been the question of renaming the trait, as it clashes with LLVM's freeze intrinsic.

I don't believe the clash to be all that relevant: LLVM's freeze (which is itself jargoney) could become a method of MaybeUninit, with namespacing providing sufficient distinction between the features. Renaming the trait could also cause confusion when approaching documents that mention Freeze, as I doubt they'd all get updated; and Freeze has been a part of the Rust jargon for a few years now.

While debating this, it's been generally agreed that the two properties that a new name should hint at are:

  • The implication of immutability, as this is the core property;
  • The restriction of that immutability to non-indirected memory, as not hinting at it could lead to great (and possibly costly to someone) confusion. I have therefore omitted Immutable from the list of options.

Here is a list of propositions collected as exhaustively as possible. My opinions on each will be surrounded by braces to keep a more compact style:

  • ShallowImmutable (a popular option, it fulfills both roles; though "shallow" may be confused for a negation of "nested", this ambiguity should, if anything, lead to a higher likelihood of people looking to disambiguate by reading the docs)
  • LocalImmutable (cut from the same cloth as ShallowImmutable)
  • InlineImmutable (again, similar structure)
  • CellFree (may be a bit too focused on implementation)
  • ValueImmutable (I dislike that one as it may get mistaken for "this is immutable unless indirected", or even "this value cannot be mutated, ever")
  • DirectImmutable (like ShallowImmutable, once more)
  • InteriorImmutable (while less eggregious than Immutable, it's the most verbose option yet while not hinting at the restriction at all).

An additional argument in favour of Freeze is the general convention that traits are named after verbs (eg. Clone and not Cloneable): conceptually, Freeze allows the obtention of a "frozen reference" which guarantees that the memory it points to cannot be modified as long as the reference lives, not even through interior mutability:

trait Freeze {
    /// Returns a `&frozen` reference to `self`, which is distinct
    /// from a common reference in that the bytes from `&self as *const Self`
    /// to `(&self as *const Self).add(1)` are guaranteed to stay the
    /// same value for the entire lifetime of the frozen reference.
    fn freeze(&self) -> &frozen Self;
}

This conceptual method is simply omitted because T: Freeze already provides the above stated guarantee for &T, and therefore &T and &frozen T are identical for T: Freeze.

While I still prefer keeping Freeze to all of those options, on the grounds that it has already settled in the Rust jargon and that the suggested names haven't seemed to spark joy either during debates, I'd emphasize that apart from those I have justified a dislike for, I'd happily support any name that gets Freeze to finally get stabilized.

The best of the names that aren't Freeze is NoCell, which Josh suggested today. It's still not a verb, unfortunately.


# Drawbacks
[drawbacks]: #drawbacks

- Some people have previously argued that this would be akin to exposing compiler internals.
- The RFC author disagrees, viewing `Freeze` in a similar light as `Send` and `Sync`: a trait that allows soundness requirements to be proven at compile time.
- `Freeze` being an auto-trait, it is, like `Send` and `Sync` a sneaky SemVer hazard.
- Note that this SemVer hazard already exists through the existence of static-promotion, as exemplified by the following example:
```rust
// old version of the crate.
mod v1 {
pub struct S(i32);
impl S {
pub const fn new() -> Self { S(42) }
}
}

// new version of the crate, adding interior mutability.
mod v2 {
use std::cell::Cell;
pub struct S(Cell<i32>);
impl S {
pub const fn new() -> Self { S(Cell::new(42)) }
}
}

// Old version: builds
const C1: &v1::S = &v1::S::new();
// New version: does not build
const C2: &v2::S = &v2::S::new();
```
- The provided example is also, in RFC author's estimation, the main way in which `Freeze` is likely to be depended upon: allowing bounds on it will likely not expand the hazard much.

# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

- The benefits of stabilizing `core::mem::Freeze` have been highlighted in [Motivation](#motivation).
- By not stabilizing `core::mem::Freeze` in trait bounds, we are preventing useful and sound code patterns from existing which were previously supported.
- Alternatively, a non-auto sub-trait of `core::mem::Freeze` may be defined:
- While this reduces the SemVer hazard by making its breakage more obvious, this does lose part of the usefulness that `core::mem::Freeze` would provide to projects such as `zerocopy`.
- A "perfect" derive macro should then be introduced to ease the implementation of this trait. A lint may be introduced in `clippy` to inform users of the existence and applicability of this new trait.

# Prior Art
[prior-art]: #prior-art
- This trait has a long history: it existed in ancient times but got [removed](https://github.com/rust-lang/rust/pull/13076) before Rust 1.0.
In 2017 it got [added back](https://github.com/rust-lang/rust/pull/41349) as a way to simplify the implementation of the `interior_unsafe` query, but it was kept private to the standard library.
In 2019, a [request](https://github.com/rust-lang/rust/issues/60715) was filed to publicly expose the trait, but not a lot happened until recently when the issue around static promotion led to it being [exposed unstably](https://github.com/rust-lang/rust/pull/121840).
- The work necessary for this RFC has already been done and merged in [this PR](https://github.com/rust-lang/rust/issues/121675), and a [tracking issue](https://github.com/rust-lang/rust/issues/121675) was opened.
- zerocopy's [`Immutable`](https://docs.rs/zerocopy/0.8.0-alpha.11/zerocopy/trait.Immutable.html) seeks to provide the same guarantees as `core::marker::Freeze`.

# Unresolved questions
[unresolved-questions]: #unresolved-questions

- Should `PhantomNotFreeze` be `Send`/`Sync`?
- RFC author is of the opinion that it should, and that `Send`/`Sync` should be addressed orthogonally.
- RFC author also indicates that if the core team wishes to consider this more deeply, the fix to `PhantomData` makes this `PhantomNotFreeze` a simple convenience type, which arguably doesn't have equivalents for `Send` and `Sync`.
- Given that removing a `Freeze` implementation from a type would only be considered a breaking change if its documentation states so, how would the standard library express which types are stably `Freeze`?
- As a blanket statement about primitive types (including function pointers)?
- How about "common sense" types, such as `IpAddr`, `Box`, `Arc`...?

# Future possibilities
[future-possibilities]: #future-possibilities

- During design meetings, the problem of auto-traits as a semver-hazard was considered more broadly, leading to the idea of a new lint.
This lint would result in a warning if code relied on a type implementing an trait that was automatically implemented for it, but that
the authors haven't opted into explicitly:
- Under these considerations, removing the auto-trait implementation of a type would no longer be considered a breaking change.
- `#[derive(Freeze, Send, Sync, Pin)]` was proposed as the way for authors to explicitly opt into these trait, making their removal a breaking change.
- Note that a syntax to express this for `async fn`'s resulting opaque type would need to be established too.
- Such a lint would have the additional benefit of helping authors spot when they accidentally remove one of these properties from their types.

- Complementary to that lint, a lint encouraging explicitly opting in or out of auto-traits that are available for a type would help raise the
awareness around auto-traits and their semver implications.

- Adding a `trait Pure: Freeze` which extends the interior immutability guarantee to indirected data could be valuable:
- This is however likely to be a fool's errand, as indirections could (for example) be hidden behind keys to global collections.
- Providing such a trait could be left to the ecosystem unless we'd want it to be an auto-trait also (unlikely).