Skip to content

Commit 15ce070

Browse files
committed
Revise MaybeUninit validity documentation
Let's rewrite this for better clarity. In particular, let's document our language guarantees upfront and in positive form. We'll then list the caveats and the non-guarantees after.
1 parent 7971b2a commit 15ce070

File tree

1 file changed

+61
-32
lines changed

1 file changed

+61
-32
lines changed

library/core/src/mem/maybe_uninit.rs

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -255,51 +255,80 @@ use crate::{fmt, intrinsics, ptr, slice};
255255
///
256256
/// # Validity
257257
///
258-
/// A `MaybeUninit<T>` has no validity requirement – any sequence of
259-
/// [bytes][reference-byte] of the appropriate length, initialized or
260-
/// uninitialized, are a valid representation of `MaybeUninit<T>`.
258+
/// `MaybeUninit<T>` has no validity requirements –- any sequence of [bytes] of
259+
/// the appropriate length, initialized or uninitialized, are a valid
260+
/// representation.
261261
///
262-
/// However, "round-tripping" via `MaybeUninit` does not always result in the
263-
/// original value. `MaybeUninit` can have padding, and the contents of that
264-
/// padding are not preserved. Concretely, given distinct `T` and `U` where
265-
/// `size_of::<T>() == size_of::<U>()`, the following code is not guaranteed to
266-
/// be sound:
262+
/// Using `MaybeUninit` to perform a round trip by transmuting a value of type
263+
/// `T` first to type `MaybeUninit<U>` (where type `U` has the same size as `T`)
264+
/// and then back to type `T` is guaranteed to be sound and to produce the
265+
/// original value with its original [provenance] if and only if no bytes in the
266+
/// representation of the value are initialized at byte offsets where type `U`
267+
/// has padding.
268+
///
269+
/// For example, due to the fact that the type `[u8; size_of::<T>]` has no
270+
/// padding, the following is sound for any type `T`:
267271
///
268272
/// ```rust,no_run
269273
/// # use core::mem::{MaybeUninit, transmute};
270-
/// # struct T; struct U;
274+
/// # struct T;
271275
/// fn identity(t: T) -> T {
272276
/// unsafe {
277+
/// let u: MaybeUninit<[u8; size_of::<T>()]> = transmute(t);
278+
/// transmute(u) // OK.
279+
/// }
280+
/// }
281+
/// ```
282+
///
283+
/// Note: Copying a value that contains references may implicitly reborrow them,
284+
/// and that may affect the value's provenance. In this respect, `identity`
285+
/// behaves in exactly the same way as does the trivial identity function:
286+
///
287+
/// ```rust,no_run
288+
/// fn trivial_identity<T>(t: T) -> T { t }
289+
/// ```
290+
///
291+
/// Note: Moving or copying a value whose representation has initialized bytes
292+
/// at byte offsets where the type has padding may lose the value of those
293+
/// bytes, so while the original value will be preserved, the original
294+
/// *representation* of that value as bytes may not be. Again, in this respect,
295+
/// `identity` behaves in exactly the same way as does `trivial_identity`.
296+
///
297+
/// Note: Performing this round trip when type `U` has padding at byte offsets
298+
/// where the representation of the original value has initialized bytes may
299+
/// produce undefined behavior or a different value. For example, the following
300+
/// is unsound since `T` requires all bytes to be initialized:
301+
///
302+
/// ```rust,no_run
303+
/// # use core::mem::{MaybeUninit, transmute};
304+
/// #[repr(C)] struct T([u8; 4]);
305+
/// #[repr(C)] struct U(u8, u16);
306+
/// fn unsound_identity(t: T) -> T {
307+
/// unsafe {
273308
/// let u: MaybeUninit<U> = transmute(t);
274-
/// transmute(u)
309+
/// transmute(u) // UB.
275310
/// }
276311
/// }
277312
/// ```
278313
///
279-
/// If the representation of `t` contains initialized bytes at byte offsets
280-
/// where `U` contains padding bytes, these may not be preserved in
281-
/// `MaybeUninit<U>`. Transmuting `u` back to `T` (i.e., `transmute(u)` above)
282-
/// may thus be undefined behavior or yield a value different from `t` due to
283-
/// those bytes being lost. This is an active area of discussion, and this code
284-
/// may become sound in the future.
285-
///
286-
/// However, so long as no such byte offsets exist, then the preceding
287-
/// `identity` example *is* sound. In particular, since `[u8; N]` has no padding
288-
/// bytes, transmuting `t` to `MaybeUninit<[u8; size_of::<T>]>` and back will
289-
/// always produce the original value `t` again. This is true even if `t`
290-
/// contains [provenance]: the resulting value will have the same provenance as
291-
/// the original `t`.
292-
///
293-
/// Note a potential footgun: if `t` contains a reference, then there may be
294-
/// implicit reborrows of the reference any time it is copied, which may alter
295-
/// its provenance. In that case, the value returned by `identity` may not be
296-
/// exactly the same as its argument. However, even in this case, it remains
297-
/// true that `identity` behaves the same as a function that just returns `t`
298-
/// immediately (i.e., `fn identity<T>(t: T) -> T { t }`).
314+
/// Conversely, the following is sound, since `T` allows uninitialized bytes in
315+
/// the representation of a value, but the round trip may alter the value:
299316
///
300-
/// [provenance]: crate::ptr#provenance
317+
/// ```rust,no_run
318+
/// # use core::mem::{MaybeUninit, transmute};
319+
/// #[repr(C)] struct T(MaybeUninit<[u8; 4]>);
320+
/// #[repr(C)] struct U(u8, u16);
321+
/// fn non_identity(t: T) -> T {
322+
/// unsafe {
323+
/// // May lose an initialized byte.
324+
/// let u: MaybeUninit<U> = transmute(t);
325+
/// transmute(u)
326+
/// }
327+
/// }
328+
/// ```
301329
///
302-
/// [reference-byte]: ../../reference/memory-model.html#bytes
330+
/// [bytes]: ../../reference/memory-model.html#bytes
331+
/// [provenance]: crate::ptr#provenance
303332
#[stable(feature = "maybe_uninit", since = "1.36.0")]
304333
// Lang item so we can wrap other types in it. This is useful for coroutines.
305334
#[lang = "maybe_uninit"]

0 commit comments

Comments
 (0)