Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] core::marker::Freeze in bounds #3633

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
21 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
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
66 changes: 66 additions & 0 deletions text/0000-stabilize-marker-freeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
- 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.

# Motivation
[motivation]: #motivation

In some contexts, guaranteeing that a value cannot be modified is a requirement to a construct behaving properly (`const` references, keys of a structured map...).
p-avital marked this conversation as resolved.
Show resolved Hide resolved

While this looks achievable by only exposing immutable references, this is actually insufficient due to the (necessary) existence of interior mutability.

Internally, the compiler uses `core::marker::Freeze` to identify types that are known not to have interior mutability.

This RFC seeks to stabilize this trait for trait bounds for the following reasons:
- As explained above, some existing constructs are left to "trust" that the values they'd need to freeze won't be mutated through interior mutability. This is a potential bug source for these constructs.
- With [this PR](https://github.com/rust-lang/rust/issues/121250), a breaking change was introduced (and stabilized in 1.78):
- This change prevents associated consts from containing references to values that don't implement `core::marker::Freeze`. Since this bound cannot be added to generics, this prevents associated consts from containing references to generics altogether.
- This change was done in order to prevent potential unsoundness, but it also completely prevents sound uses, such as the ones reported in [this regression issue](https://github.com/rust-lang/rust/issues/123281).
p-avital marked this conversation as resolved.
Show resolved Hide resolved

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

This RFC simply seeks to act as the stabilization RFC for `core::marker::Freeze` in trait bounds.

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation
p-avital marked this conversation as resolved.
Show resolved Hide resolved

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.

However, it was deemed that adding an Auto Trait to Rust's public API should go through the proper RFC process, but the RFC was never created.
p-avital marked this conversation as resolved.
Show resolved Hide resolved

# 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.

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

- This trait has existed for 7 years. Over this course, it was made a compiler internal temporarily, but its usefulness lead to it being re-exposed soon after.
p-avital marked this conversation as resolved.
Show resolved Hide resolved
p-avital marked this conversation as resolved.
Show resolved Hide resolved
- 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.

# Prior art
p-avital marked this conversation as resolved.
Show resolved Hide resolved
[prior-art]: #prior-art

This feature has been available in `nightly` for 7 years, and is used internally by the compiler.

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

[Should the trait be exposed under a different name?](https://github.com/rust-lang/rust/pull/121501#issuecomment-1962900148)
Copy link

Choose a reason for hiding this comment

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

Something that seems troublesome about Freeze is that it's a trait named using a verb, which is good in isolation, but that verb would usually be an operation you can perform on values of the implementing type. What's the “freeze” operation on T: Freeze? I suppose it's taking a & reference to T. That's reasonable enough, but there is another “freeze” operation that's both already named in LLVM and recently been proposed for addition to Rust: “freezing” uninitialized memory to enable safely reading the bytes of a value that contains padding, or reading a value that may be partially initialized. That's a different thing in the same problem domain (what you can or can't do with the bytes when you have a T or &T) which seems dangerously confusing. And outside of that name collision, “Freeze” doesn't convey much to the naïve reader.

Unfortunately, I don't have any good ideas for alternatives, especially not non-negated ones as @RalfJung suggested, but I think that stabilizing this under the name Freeze would create a frequent source of confusion among those encountering it for the first time. It might be better to have an awkward complex name than a cryptic simple one. Awkward suggestion: ShallowImmut?

Copy link
Member

Choose a reason for hiding this comment

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

what about Stuck? that is unused afaict so people seeing it would think to look up what it means in Rust. It's also nice and short and reminds users of what it means.

Copy link
Member

@RalfJung RalfJung May 17, 2024

Choose a reason for hiding this comment

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

At least on the theory side, a program being "stuck" has meaning already -- it means that execution can't progress further as the program has left the realm of well-defined executions. I would find this term very confusing.

"Frozen" is better than "Freeze" as it's not an action.

Unfortunately, I don't have any good ideas for alternatives, especially not rust-lang/rust#121501 (comment), but I think that stabilizing this under the name Freeze would create a frequent source of confusion among those encountering it for the first time.

Maybe we shouldn't call that "Freeze"? It's not a great name as what the operation really does is turn poison into non-deterministic regular bytes. Rust doesn't have undef-style "bytes that are unstable".

Copy link
Member

Choose a reason for hiding this comment

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

At least on the theory side, a program being "stuck" has meaning already -- it means that execution can't progress further as the program has left the realm of well-defined executions. I would find this term very confusing.

I was thinking more of that the value in memory is stuck at whatever bytes are put into .rodata. because Stuck is a trait and applies to types, not program execution, it seems different enough to me that it won't be that confusing.

Maybe we shouldn't call that "Freeze"? It's not a great name as what the operation really does is turn poison into non-deterministic regular bytes. Rust doesn't have undef-style "bytes that are unstable".

since the LLVM IR op is already called freeze, they've effectively already laid claim to that name, so anyone who's used LLVM IR and tries to figure out rust will be confused if we use freeze to mean something completely different where it could be confused with an operation (since freeze is present-tense, unlike stuck where being past-tense and being applied to types makes it much less likely to be an operation).

Copy link
Member

Choose a reason for hiding this comment

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

I was thinking more of that the value in memory is stuck at whatever bytes are put into .rodata

That's far from it, though. Freeze isnt just about rodata, it's about all shared references without interior mutability.

I find "stuck" extremely unintuitive. It conveys negative feelings to me, "we're stuck somewhere or with something (and want to get unstuck again)". I don't think it is a good term for "this memory is immutable".

since the LLVM IR op is already called freeze, they've effectively already laid claim to that name, so anyone who's used LLVM IR and tries to figure out rust will be confused if we use freeze to mean something completely different where it could be confused with an operation (since freeze is present-tense, unlike stuck where being past-tense and being applied to types makes it much less likely to be an operation).

LLVM also uses poison/undef, neither of which we use in Rust (we call it "uninitialized memory" or just "uninit"). LLVM calls it getelementptr or ptraddr, we call it offset. LLVM calls it load/store, we call it read/write. The list can probably go on for a while. I don't think we should let LLVM dictate our terminology.

Copy link
Contributor

Choose a reason for hiding this comment

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

In zerocopy, we went with Immutable, which I quite like (credit to @jswrenn's coworker for suggesting the name). It is an adjective and accurately describes the semantics - T: Immutable means that T cannot be mutated behind a shared reference.

Copy link
Member

Choose a reason for hiding this comment

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

but T can be mutated behind a shared reference if that mutation is hidden in some other block of memory: Box<Cell<u8>> does implement Freeze, but can be mutated behind a shared reference. therefore I think using Immutable would be highly misleading.

Copy link
Contributor

Choose a reason for hiding this comment

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

Arguably any name that doesn't clarify that the property only applies shallowly/not-via-indirection is similarly misleading. If we're worried about that, we'd need to add an adjective like Shallow to the name.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe just invent another term that no one else used. Freeze itself is an invented term anyway.

Perhaps Solid (you "freeze" liquid to get "solid" 🤷).
Or Acellular (technically a negated word).

But given that the trait has existed for so long under the name Freeze perhaps it has already gotten traction

Though IMO it's fine to actually use a multi-word term like ShallowImmutable or CellFree? We already have got the the two-word UnwindSafe and three-word RefUnwindSafe auto traits in std. As long as this trait bound is not needed very often (unlike Send and Sync and Sized) using a longer but more precise name shouldn't be a problem.

Copy link
Author

Choose a reason for hiding this comment

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

Let me set a record for the most negative name: UnindirectedUnUnsafeCelled 😄

I like ShallowImmutable a lot, as it's rather descriptive and attracts curiosity thanks to the Shallow term.

Do we have any opposition to ShallowImmutable?


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

One might later consider whether `core::mem::Freeze` should be allowed to be `unsafe impl`'d like `Send` and `Sync` are, possibly allowing wrappers around interiorly mutable data to hide this interior mutability from constructs that require `Freeze` if the logic surrounding it guarantees that the interior mutability will never be used.

This consideration is purposedly left out of scope for this RFC to allow the stabilization of its core interest to go more smoothly; these two debates being completely orthogonal.