Summary
Introduce a Immutable trait which indicates that a type contains no UnsafeCells. Allow FromZeros, FromBytes, and AsBytes on types with UnsafeCells and use Immutable as a bound on individual methods/functions.
This enables by-value transmutations such as transmute! on types containing UnsafeCells.
See also #694.
Progress
Details
We require that FromZeros, FromBytes, and AsBytes types don't permit interior mutability. This is a requirement in order for reference casts (e.g., &T to &[u8] or vice-versa) to be valid, but is not a requirement for value casts. As a result, we don't support some transmutations that are sound (namely, value transmutations on types with interior mutability).
If we added a separate Immutable trait to describe this "has no UnsafeCells" property, then we could decouple it from the other traits and allow them to support types with UnsafeCells. Immutable would then be required as a bound on some functions/methods/macros. This would make our API somewhat more complicated, but would unlock a new class of use cases.
Note that Immutable would be equivalent to the stdlib-internal Freeze trait. There is a proposal to make that trait public, but we shouldn't bank on that happening any time soon.
TryFromBytes and unsized UnsafeCell
In principle, there's no reason we couldn't implement FromZeros, FromBytes, and AsBytes for UnsafeCell<T> even when T: ?Sized. However, this would not play nicely with TryFromBytes (currently in progress in #5), which we want to eventually be a super-trait of FromZeros.
In particular, in order to implement TryFromBytes for a type, we have to implement the is_bit_valid method. For UnsafeCell<T> where T: Sized, we can make a copy of the referent UnsafeCell<T>, transmute it to a MaybeUninit<T>, and then construct a Ptr<T> to that value. This workaround isn't possible for unsized UnsafeCells.
Given that the whole problem with UnsafeCells arises when they're handled by-reference, and given that the only way to operate on unsized values in Rust (at least today) is by reference, supporting unsized UnsafeCells is unlikely to be useful to any of our users. Thus, by implementing TryFromBytes (and, by implication, the other traits) only for sized UnsafeCell, we get most of the benefit.
Motivation
This was originally motivated by chipsalliance/caliptra-sw#634. @korran has provided this minimized example to motivate the feature request:
#[repr(C)]
pub struct Prng {
// It is critical for safety that every bit-pattern of this struct
// is valid (no padding, no enums, no references), similar to the requirements for
// zerocopy::FromBytes.
s0: Cell<u32>,
s1: Cell<u32>,
s2: Cell<u32>,
s3: Cell<u32>,
}
impl Prng {
/// # Safety
///
/// Caller must verify that the memory locations between `addr` and
/// `addr + size_of::<Prng>()` are valid and meet the alignment
/// requirements of Prng, and are not used for anything else.
pub unsafe fn from_address(addr: u32) -> &'static Self {
&*(addr as *const Self)
}
pub fn next(&self) -> u32 { ... }
}
If FromBytes didn't forbid interior mutability, the API of from_address would be the same, but it would not require the authors of from_address to justify that it's sound to construct a Prng at the given memory location regardless of its previous contents. Instead, the safety argument can just be "Prng: FromBytes, so it's sound regardless of the memory contents".
Summary
Introduce a
Immutabletrait which indicates that a type contains noUnsafeCells. AllowFromZeros,FromBytes, andAsByteson types withUnsafeCells and useImmutableas a bound on individual methods/functions.This enables by-value transmutations such as
transmute!on types containingUnsafeCells.See also #694.
Progress
ImmutabletraitNoCell#656ImmutableNoCell#667FromZeros,FromBytes, andAsBytesonUnsafeCell; addImmutablebound on methods as neededNoCellpermitUnsafeCells #682AsBytestoIntoBytes#695TryFromBytes,FromZeros, andFromBytesfor unsizedUnsafeCell. See the discussion below for why we originally limited these impls to sizedUnsafeCells only.TryFromBytesonUnsafeCell; addImmutablebound ontry_from_refmethodTryFromBytes] PermitUnsafeCells#890#[doc(hidden)]fromImmutableandpub use zerocopy_derive::ImmutableNoCell#977Immutableas merely a polyfill for that traitUnsafeCells, but not necessary, and that callers cannot assume thatT: Immutableimplies thatTcontains noUnsafeCells. Note that our code will need to rely on this implication, and all safety comments should be updated to point at an issue that tracks this change.Consider adding blanket traits:RefFromZerosforFromZeros + Immutable,RefFromBytesforFromBytes + Immutable, andRefAsBytesforAsBytes + ImmutableImmutable#1296Details
We require that
FromZeros,FromBytes, andAsBytestypes don't permit interior mutability. This is a requirement in order for reference casts (e.g.,&Tto&[u8]or vice-versa) to be valid, but is not a requirement for value casts. As a result, we don't support some transmutations that are sound (namely, value transmutations on types with interior mutability).If we added a separate
Immutabletrait to describe this "has noUnsafeCells" property, then we could decouple it from the other traits and allow them to support types withUnsafeCells.Immutablewould then be required as a bound on some functions/methods/macros. This would make our API somewhat more complicated, but would unlock a new class of use cases.Note that
Immutablewould be equivalent to the stdlib-internalFreezetrait. There is a proposal to make that trait public, but we shouldn't bank on that happening any time soon.TryFromBytesand unsizedUnsafeCellIn principle, there's no reason we couldn't implement
FromZeros,FromBytes, andAsBytesforUnsafeCell<T>even whenT: ?Sized. However, this would not play nicely withTryFromBytes(currently in progress in #5), which we want to eventually be a super-trait ofFromZeros.In particular, in order to implement
TryFromBytesfor a type, we have to implement theis_bit_validmethod. ForUnsafeCell<T>whereT: Sized, we can make a copy of the referentUnsafeCell<T>, transmute it to aMaybeUninit<T>, and then construct aPtr<T>to that value. This workaround isn't possible for unsizedUnsafeCells.Given that the whole problem with
UnsafeCells arises when they're handled by-reference, and given that the only way to operate on unsized values in Rust (at least today) is by reference, supporting unsizedUnsafeCells is unlikely to be useful to any of our users. Thus, by implementingTryFromBytes(and, by implication, the other traits) only for sizedUnsafeCell, we get most of the benefit.Motivation
This was originally motivated by chipsalliance/caliptra-sw#634. @korran has provided this minimized example to motivate the feature request:
If
FromBytesdidn't forbid interior mutability, the API offrom_addresswould be the same, but it would not require the authors offrom_addressto justify that it's sound to construct aPrngat the given memory location regardless of its previous contents. Instead, the safety argument can just be "Prng: FromBytes, so it's sound regardless of the memory contents".