-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Request: Make std::marker::Freeze
pub again
#60715
Comments
cc @rust-lang/lang and possibly @rust-lang/libs I'm sorta interested in this direction. However, I would also be interested in seeing more use cases to justify the exposure of |
Note that this would make it an api-breaking change to add interior mutability to a type. |
Adding interior mutability into a type is already an API breaking change. Even adding trait objects into a type is an API breaking change. struct S {
i: usize,
//j: std::cell::Cell<usize>,
//k: Box<dyn std::error::Error>,
}
fn f(s: &S) {
let _ = std::panic::catch_unwind(|| {
let _s = s;
});
}
fn main() {} |
This is essentially the same use case, but
Fair point! This is already the case for |
@dtolnay We might want to add a |
@Centril That's equivalent to |
Cc #25053 -- using |
We'd very much appreciate if you wouldn't do that. This would paint us into a corner that is impossible to get out of, where the legitimate usecases for a |
@glaebhoerl Ahh, I didn't realize there was a discussion around that. It's not clear to me that
For that to work, the proposed version of |
Note that |
To opt out of asideI think that having an explicitely named |
A separate
|
You can make your own on stable Rust with a wrapper around Note that we reserve the right to consider everything outside a literal |
Okay, seeing this issue has convinced me that we really need to push for either opt-in Copy, or Copy for UnsafeCell, or the CopyOwn thing. Because we cannot just wait and expect people not tor rely on people abusing the number of things Copy excludes for other purposes than declaring things to be (implicitly) byte-copyable. People already abuse it for making sure a type has a no-op Drop impl, which is its own headache that is further exacerbated by the fact that UnsafeCell can't implement Copy. |
Mem-mapping is a use-case I have for Freeze, specifically to prevent accidentally implementing some traits on types with interior mutability. Though given how much unsafe code is involved in that use-case anyway it'd be just a "belt-and-suspenders" measure. It's also a use-case where exposing the actual compiler |
I don't entirely follow. Note that |
On January 1, 2020 12:42:22 PM EST, Ralf Jung ***@***.***> wrote:
@petertodd
> It's also a use-case where exposing the actual compiler Freeze type
would be annoying: in my usecase a small subset of types have both
interior mutability and implement mem-mapping, with care taken to only
actually mutate "dirty" values that are heap allocated. So those types
would need Freeze impl's, which is probably inappropriate if they
actually affect code generation.
I don't entirely follow. Note that `Freeze` only talks about the value
itself, not other memory it points to. So, for example,
`Box<RefCell<T>>` is `Freeze`.
I'm talking about values in memmapped files.
To summarize, I have a trait, ValidateBlob, that validates that a blob of bytes are structurally valid for the type implementing it ([repr(C)], unaligned types, etc). It's designed to work with read-only mappings¹, so interior mutability will simply segfault². Freeze in that context would be a useful lint. But ValidateBlob is of course unsafe, so it'd just be a lint.
1) I'm both willing to live with corruption caused by unintentional modifications to the backing file, *and* in this use case since the backend is meant to be append only, you can use the append-only bit to make that problem much less of an issue. It's basically a database after all. You can also play games with memmapping modes to get similar results, with different trade-offs.
2) Intentional mutation is via copy-on-write, so all intentional mutation will be in heap/stack allocated memory.
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Yes it would |
In #121250 another case came up where a I am more and more in agreement that we should allow |
Don't we already have such a trait with |
In the previous RFC for stabilizing Freeze, t-lang said this
However, this is already a breaking change. Various aspects of working with data in They also suggested experimenting with this as a library crate, but that doesn't work for issues like #121250.
The difference is that Sized cannot be implemented anywhere. Freeze should be implementable with a nightly feature but not without. |
Can't we do that by just using the Sealed trait trick to make implementations impossible?
…On February 22, 2024 11:55:53 AM GMT+01:00, Ralf Jung ***@***.***> wrote:
In #121250 another case came up where a `Freeze` bound would be very useful.
I am more and more in agreement that we should allow `Freeze` bounds on stable. We should *not* allow any `Freeze` impls though. Do we have any way of marking a trait as accessible on stable only for bounds but not for impls?
|
Why should |
No, Freeze is an auto trait.
This is the current definition: #[lang = "freeze"]
pub(crate) unsafe auto trait Freeze {}
impl<T: ?Sized> !Freeze for UnsafeCell<T> {}
marker_impls! {
unsafe Freeze for
{T: ?Sized} PhantomData<T>,
{T: ?Sized} *const T,
{T: ?Sized} *mut T,
{T: ?Sized} &T,
{T: ?Sized} &mut T,
} If we make it not implementable, we have to completely change how it works. |
For Sized we have a bunch of builtin impls. We can have the same for Freeze if we were to deny user implementations. Or we could restrict impls to just libcore. For example by renaming Freeze to FreezeInternal and then export a new Freeze trait which requires FreezeInternal and has a blanket impl. This should prevent end users from manually implementing it, right? |
Various hacks are possible. But TBH just checking a feature flag when there is an impl seems easiest... |
Could it at least be an internal feature to indicate that it will never be stabilized? |
Could you justify the term of "hack" regarding @bjorn3's suggestion? It is a bit (too) easy to dismiss an ideä throwing that word around; in this instance we have:
It turns out that being unsafe
impl<T> StaticPromotable for T
where
T : ShallowlyImmutableWhenShared,
{} And conservatively, the stdlib could make it necessary as well, to avoid user-provided impls: pub unsafe trait StaticPromotable : ShallowlyImmutableWhenShared {} So, while the two notions are quite entangled given the equivalence property that the super-bound + blanket impl entail, it still looks legitimate to consider there being two interfaces, with one focusing on the raw byte-level properties of a type, and the other, on the usability that such a property offers. For context, consider the error we currently get with: trait Trait<T: 'static> {
const VALUE: T;
const VALUE_REF: &'static T = &Self::VALUE;
} it complains of
If, however, the compiler required error[E0277]: `T` cannot be promoted to static storage
--> src/lib.rs:3:42
|
3 | const VALUE_REF: &'static T = &{ Self::VALUE };
| ^^^^^^^^^^^^^^^ `T` cannot be promoted to static storage.
|
= help: the trait `StaticPromotable` is not implemented for `T`
= note: static promotion can only be applied to values whose type implements `StaticPromotable` Which would be way more similar to other such errors in Rust (e.g., a It is a name (modulo bikeshed) which expresses the property the user is most likely interested in, rather than hearing of
The fact is, there are cases where users are direly in need of just knowing, talking, and often actually using the |
I was interpreting it as just a somewhat roundabout way to achieve the goal of "having a trait that we can use as a bound but not implement". There was no further motivation given. The goal of separating "static promotability" from "no interior mutability" was not articulated here before (or if it was then I missed that), so you can't expect me to take it into account. I can see that if we want to separate these two things, then (your framing of) @bjorn3's proposal makes sense. But why should we want to have that split in the first place? |
Also note that
const C: &'static Vec<i32> = &{ Vec::new() }; This is not promotion, this is the "outer scope" rule applied top-level items. (The fact that this rule applies here is causing pain and bugs and confusion for years now, but it's way too late to take this back.) |
Nominating for lang discussion based on @RalfJung's comments at #60715 (comment) and #60715 (comment) . |
@joshtriplett it may make more sense to nominate #121501, which is a concrete proposal. Or at least you should consider that PR as part of your discussion. :) |
@RalfJung Fair enough; I can link to this one for context. |
This is actually key to |
Expose the Freeze trait again (unstably) and forbid implementing it manually non-emoji version of rust-lang#121501 cc rust-lang#60715 This trait is useful for generic constants (associated consts of generic traits). See the test (`tests/ui/associated-consts/freeze.rs`) added in this PR for a usage example. The builtin `Freeze` trait is the only way to do it, users cannot work around this issue. It's also a useful trait for building some very specific abstrations, as shown by the usage by the `zerocopy` crate: google/zerocopy#941 cc `@RalfJung` T-lang signed off on reexposing this unstably: rust-lang#121501 (comment)
Expose the Freeze trait again (unstably) and forbid implementing it manually non-emoji version of rust-lang#121501 cc rust-lang#60715 This trait is useful for generic constants (associated consts of generic traits). See the test (`tests/ui/associated-consts/freeze.rs`) added in this PR for a usage example. The builtin `Freeze` trait is the only way to do it, users cannot work around this issue. It's also a useful trait for building some very specific abstrations, as shown by the usage by the `zerocopy` crate: google/zerocopy#941 cc ``@RalfJung`` T-lang signed off on reexposing this unstably: rust-lang#121501 (comment)
Expose the Freeze trait again (unstably) and forbid implementing it manually non-emoji version of rust-lang#121501 cc rust-lang#60715 This trait is useful for generic constants (associated consts of generic traits). See the test (`tests/ui/associated-consts/freeze.rs`) added in this PR for a usage example. The builtin `Freeze` trait is the only way to do it, users cannot work around this issue. It's also a useful trait for building some very specific abstrations, as shown by the usage by the `zerocopy` crate: google/zerocopy#941 cc ```@RalfJung``` T-lang signed off on reexposing this unstably: rust-lang#121501 (comment)
Rollup merge of rust-lang#121840 - oli-obk:freeze, r=dtolnay Expose the Freeze trait again (unstably) and forbid implementing it manually non-emoji version of rust-lang#121501 cc rust-lang#60715 This trait is useful for generic constants (associated consts of generic traits). See the test (`tests/ui/associated-consts/freeze.rs`) added in this PR for a usage example. The builtin `Freeze` trait is the only way to do it, users cannot work around this issue. It's also a useful trait for building some very specific abstrations, as shown by the usage by the `zerocopy` crate: google/zerocopy#941 cc ```@RalfJung``` T-lang signed off on reexposing this unstably: rust-lang#121501 (comment)
Expose the Freeze trait again (unstably) and forbid implementing it manually non-emoji version of rust-lang#121501 cc rust-lang#60715 This trait is useful for generic constants (associated consts of generic traits). See the test (`tests/ui/associated-consts/freeze.rs`) added in this PR for a usage example. The builtin `Freeze` trait is the only way to do it, users cannot work around this issue. It's also a useful trait for building some very specific abstrations, as shown by the usage by the `zerocopy` crate: google/zerocopy#941 cc ```@RalfJung``` T-lang signed off on reexposing this unstably: rust-lang#121501 (comment)
Expose the Freeze trait again (unstably) and forbid implementing it manually non-emoji version of rust-lang#121501 cc rust-lang#60715 This trait is useful for generic constants (associated consts of generic traits). See the test (`tests/ui/associated-consts/freeze.rs`) added in this PR for a usage example. The builtin `Freeze` trait is the only way to do it, users cannot work around this issue. It's also a useful trait for building some very specific abstrations, as shown by the usage by the `zerocopy` crate: google/zerocopy#941 cc ```@RalfJung``` T-lang signed off on reexposing this unstably: rust-lang#121501 (comment)
I had heard tell of
Freeze
but didn't really know what it was until today.swym
, a hybrid transactional memory library, has an accidental reimplementation ofFreeze
usingoptin_builtin_traits
. Unfortunatelyoptin_builtin_traits
is the only feature keepingswym
on nightly.The ticket that removed
Freeze
doesn't have much of an explanation for why it was removed so I'm assuming it was a lack of motivating use cases.Use Case
swym::tcell::TCell::borrow
returns snapshots of data - shallowmemcpy
s - that are guaranteed to not be torn, and be valid for the duration of the transaction. Those snapshots hold on to the lifetime of theTCell
in order to act like a true reference, without blocking updates to theTCell
from other threads. Other threads promise to not mutate the value that had its snapshot taken until the transaction has finished, but are permitted to move the value in memory.This works great for a lot of types, but fails miserably when
UnsafeCell
s are directly stored in the type.Even if
Mutex
internally had a pointer to the "actual" mutex data structure, the above example would still be broken because theString
is deallocated through the shallow copy. TheString
contained in theTCell
would point to freed memory.Note that having
TCell::borrow
requireSync
would still allow the above broken example to compile.Freeze
If
swym::tcell::TCell::borrow
could requireFreeze
then this would not be an issue as theMutex
type is definitely notFreeze
.Shallow immutability is all that is required for
TCell::borrow
to work.Sync
is only necessary to makeTCell
Sync
.TCell<String>
- should be permitted.TCell<Mutex<String>>
- should be forbidden.TCell<Box<Mutex<String>>>
- should be permitted.Alternatives
MyFreeze
was correct when it was written. Everytime the author ofMyType
updates their dependency onother_crate
they must recheck thatOtherType
still has no direct interior mutability or risk unsoundness.Add a
T: Copy
bound onTCell::<T>::borrow
. This would definitely work but leaves a lot of types out.Wait for OIBITs to stabilize (assuming it will be stabilized).
Have
TCell
store aBox<T>
internally, and only work with heap allocated data where interior mutability is of no concern. This would be pretty effective, and if the type is small enough andCopy
, theBox
could be elided. While not as good as stabilizingFreeze
, I think this is the best alternative.The text was updated successfully, but these errors were encountered: