Should `Copy` require `Freeze`?

Freeze is a lang-item marker auto-trait that indicates that a type does not contain any shared mutability (UnsafeCell) before indirection.

This property is observable on stable (and has been for a long while) even though the trait is unstable; it is only allowed to take a reference to a place in const context if that place was already behind a reference or has a Freeze type (feature(const_refs_to_cell)).

For a #[repr(packed)] type, the built-in #[derive] work by copying from the fields, since it's unsafe to reference an underaligned field. However, since the field is copied to the stack, if it were to contain any shared mutability, any writes from the delegated to trait implementation would get silently discarded.

This scenario is impossible so long as a type being Copy also requires it to be Freeze. UnsafeCell is not Copy, thus implementing Copy will indirectly require Freeze. However, I recall discussion saying that Cell<impl Copy> could potentially be made to impl Copy, it would just be a bit of a footgun similar to iterators, mutating a copy but expecting to change the original source as well.

I think it could be prudent to just make Freeze into a supertrait of Copy (and thus finalize that Cell must never be Copy). This turns an informal expectation into a stable guarantee that can then be relied upon.

A Copy + !Freeze field doesn't make packed derives unsound, since no unsafe is involved, but it would certainly be surprising; especially so if the derive ever chooses to bypass the copy for fields that are already guaranteed to be sufficiently aligned.

7 Likes

Example of a “Copy + !Freeze”-like edge case in Swift: Swift Regret: Lazy Vars in Structs // -dealloc

Not the end of the world, not even that hard to reason about, but you have to remember to reason about it.

4 Likes

There's very handy from_mut and as_slice_of_cells that can be called on Copy types.

I don't see the relevance of Cell::from_mut here? It is currently true that all Copy types are by construction also Freeze, independent of the legality of &mut T -> &Cell<T>.

Is it valid to freeze and unfreeze memory? I assume that bytes inside &mut [u8] are freeze, but they can be cast to be inside an UnsafeCell, even though u8 doesn't have interior mutability and is Copy. Is &mut sufficient to make this irrelevant?

T: Freeze is about the property of &T. Those functions are about &mut T, &Cell<T> and &[Cell<T>] but not &T.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.