From 91e99637e0c9f72abe9be326f16fee7fd9bef1b7 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 7 Jun 2026 23:01:46 +0000 Subject: [PATCH 1/6] Define pointer metadata The pointer-to-pointer cast rules and the wide-pointer validity rule both speak of the *metadata* of a pointer, but we hadn't explicitly defined the term (even though we had defined the contents of that metadata). Let's do that and link to it. --- src/behavior-considered-undefined.md | 9 +++++---- src/dynamically-sized-types.md | 6 +++--- src/expressions/operator-expr.md | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index a99ae38850..2229878b71 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -100,7 +100,7 @@ r[undefined.dangling.zero-size] If the [size is 0][zero-sized], then the pointer is trivially never "dangling" (even if it is a null pointer). r[undefined.dangling.dynamic-size] -Note that dynamically sized types (such as slices and strings) point to their entire range, so it is important that the length metadata is never too large. +Note that dynamically sized types (such as slices and strings) point to their entire range, so it is important that the length [metadata] is never too large. r[undefined.dangling.alloc-limit] In particular, the dynamic size of a Rust value (as determined by `size_of_val`) must never exceed `isize::MAX`, since it is impossible for a single allocation to be larger than `isize::MAX`. @@ -142,10 +142,10 @@ r[undefined.validity.union] * For a `union`, the exact validity requirements are not decided yet. Obviously, all values that can be created entirely in safe code are valid. If the union has a [zero-sized] field, then every possible value is valid. Further details are [still being debated](https://github.com/rust-lang/unsafe-code-guidelines/issues/438). r[undefined.validity.reference-box] -* A reference or [`Box`] must be aligned and non-null, it cannot be [dangling], and it must point to a valid value (in case of dynamically sized types, using the actual dynamic type of the pointee as determined by the metadata). Note that the last point (about pointing to a valid value) remains a subject of some debate. +* A reference or [`Box`] must be aligned and non-null, it cannot be [dangling], and it must point to a valid value (in case of dynamically sized types, using the actual dynamic type of the pointee as determined by the [metadata]). Note that the last point (about pointing to a valid value) remains a subject of some debate. r[undefined.validity.wide] -* The metadata of a wide reference, [`Box`], or raw pointer must match the type of the unsized tail: +* The [metadata] of a wide reference, [`Box`], or raw pointer must match the type of the unsized tail: * `dyn Trait` metadata must be a pointer to a compiler-generated vtable for `Trait`. (For raw pointers, this requirement remains a subject of some debate.) * Slice (`[T]`) metadata must be a valid `usize`. Furthermore, for wide references and [`Box`], slice metadata is invalid if it makes the total size of the pointed-to value bigger than `isize::MAX`. @@ -156,7 +156,7 @@ r[undefined.validity.valid-range] > `rustc` achieves this with the unstable `rustc_layout_scalar_valid_range_*` attributes. r[undefined.validity.const-provenance] -* **In [const contexts]**: In addition to what is described above, further provenance-related requirements apply during const evaluation. Any value that holds pure integer data (the `i*`/`u*`/`f*` types as well as `bool` and `char`, enum discriminants, and slice metadata) must not carry any provenance. Any value that holds pointer data (references, raw pointers, function pointers, and `dyn Trait` metadata) must either carry no provenance, or all bytes must be fragments of the same original pointer value in the correct order. +* **In [const contexts]**: In addition to what is described above, further provenance-related requirements apply during const evaluation. Any value that holds pure integer data (the `i*`/`u*`/`f*` types as well as `bool` and `char`, enum discriminants, and slice [metadata]) must not carry any provenance. Any value that holds pointer data (references, raw pointers, function pointers, and `dyn Trait` metadata) must either carry no provenance, or all bytes must be fragments of the same original pointer value in the correct order. This implies that transmuting or otherwise reinterpreting a pointer (reference, raw pointer, or function pointer) into a non-pointer type (such as integers) is undefined behavior if the pointer had provenance. @@ -194,6 +194,7 @@ r[undefined.validity.undef] [`target_feature`]: attributes/codegen.md#the-target_feature-attribute [`UnsafeCell`]: std::cell::UnsafeCell [Rustonomicon]: ../nomicon/index.html +[metadata]: dynamic-sized.pointer-types [`NonNull`]: core::ptr::NonNull [`NonZero`]: core::num::NonZero [place expression context]: expressions.md#place-expressions-and-value-expressions diff --git a/src/dynamically-sized-types.md b/src/dynamically-sized-types.md index cd90adb75d..5c9e69a713 100644 --- a/src/dynamically-sized-types.md +++ b/src/dynamically-sized-types.md @@ -8,9 +8,9 @@ r[dynamic-sized.restriction] Such types can only be used in certain cases: r[dynamic-sized.pointer-types] -* [Pointer types] to DSTs are sized but have twice the size of pointers to sized types - * Pointers to slices and `str` also store the number of elements. - * Pointers to trait objects also store a pointer to a vtable. +* [Pointer types] to DSTs are sized but have twice the size of pointers to sized types, since they also store *metadata*: + * Pointers to slices store the number of elements; pointers to `str` store the length in bytes. + * Pointers to trait objects store a pointer to a vtable. r[dynamic-sized.question-sized] * DSTs can be provided as type arguments to generic type parameters having the special `?Sized` bound. They can also be used for associated type definitions when the corresponding associated type declaration has a `?Sized` bound. By default, any type parameter or associated type has a `Sized` bound, unless it is relaxed using `?Sized`. diff --git a/src/expressions/operator-expr.md b/src/expressions/operator-expr.md index 6c5e4ffbf1..c692122bf4 100644 --- a/src/expressions/operator-expr.md +++ b/src/expressions/operator-expr.md @@ -732,7 +732,7 @@ r[expr.as.pointer.sized] > ``` r[expr.as.pointer.discard-metadata] -- If `T` is unsized and `U` is sized, the cast discards all metadata that completes the wide pointer `T` and produces a thin pointer `U` consisting of the data part of the unsized pointer. +- If `T` is unsized and `U` is sized, the cast discards all [metadata] that completes the wide pointer `T` and produces a thin pointer `U` consisting of the data part of the unsized pointer. > [!EXAMPLE] > ```rust @@ -1244,6 +1244,7 @@ As with normal assignment expressions, compound assignment expressions always pr [logical not]: ../types/boolean.md#logical-not [logical or]: ../types/boolean.md#logical-or [logical xor]: ../types/boolean.md#logical-xor +[metadata]: dynamic-sized.pointer-types [moved from]: expr.move.movable-place [mutable]: ../expressions.md#mutability [place expression]: ../expressions.md#place-expressions-and-value-expressions From e3cc399c8d17739bbe1228b6099ad7e03e918d57 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 7 Jun 2026 23:12:59 +0000 Subject: [PATCH 2/6] Define an unsized tail We refer to the unsized tail of a type, but we hadn't defined it. Let's do that and link to the definition. --- src/behavior-considered-undefined.md | 3 ++- src/dynamically-sized-types.md | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 2229878b71..94783af9ec 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -145,7 +145,7 @@ r[undefined.validity.reference-box] * A reference or [`Box`] must be aligned and non-null, it cannot be [dangling], and it must point to a valid value (in case of dynamically sized types, using the actual dynamic type of the pointee as determined by the [metadata]). Note that the last point (about pointing to a valid value) remains a subject of some debate. r[undefined.validity.wide] -* The [metadata] of a wide reference, [`Box`], or raw pointer must match the type of the unsized tail: +* The [metadata] of a wide reference, [`Box`], or raw pointer must match the type of the [unsized tail]: * `dyn Trait` metadata must be a pointer to a compiler-generated vtable for `Trait`. (For raw pointers, this requirement remains a subject of some debate.) * Slice (`[T]`) metadata must be a valid `usize`. Furthermore, for wide references and [`Box`], slice metadata is invalid if it makes the total size of the pointed-to value bigger than `isize::MAX`. @@ -204,6 +204,7 @@ r[undefined.validity.undef] [project-field]: expressions/field-expr.md [project-tuple]: expressions/tuple-expr.md#tuple-indexing-expressions [project-slice]: expressions/array-expr.md#array-and-slice-indexing-expressions +[unsized tail]: dynamic-sized.tail [unwinding-ffi]: panic.md#unwinding-across-ffi-boundaries [const-promoted]: destructors.md#constant-promotion [lifetime-extended]: destructors.md#temporary-lifetime-extension diff --git a/src/dynamically-sized-types.md b/src/dynamically-sized-types.md index 5c9e69a713..b14a807e83 100644 --- a/src/dynamically-sized-types.md +++ b/src/dynamically-sized-types.md @@ -25,10 +25,17 @@ r[dynamic-sized.struct-field] > [!NOTE] > [Variables], function parameters, [const] items, and [static] items must be `Sized`. +r[dynamic-sized.tail] +The *unsized tail* of a type is the dynamically sized component that the [metadata] of a pointer to the type describes. A [slice] (`[T]`) and a [`str`] are each their own unsized tail, described by a length; a [trait object] (`dyn Trait`) is its own unsized tail, described by a pointer to a vtable. When a struct (per [dynamic-sized.struct-field]) or a tuple has an unsized last field, its unsized tail is the unsized tail of that field. A sized type has no unsized tail. + +[metadata]: dynamic-sized.pointer-types [sized]: special-types-and-traits.md#sized [Slices]: types/slice.md +[slice]: types/slice.md [str]: types/str.md +[`str`]: types/str.md [trait objects]: types/trait-object.md +[trait object]: types/trait-object.md [Pointer types]: types/pointer.md [Variables]: variables.md [const]: items/constant-items.md From 3f9733ad10fde5949532e3803b55086565526fd5 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 1 Jun 2026 17:09:57 +0000 Subject: [PATCH 3/6] Document layout equivalences for pointer types We guarantee various things about the layout of pointers and references, but we'd made no guarantees of equivalence between two pointers to distinct unsized types that differ in the unsized tail. Let's make some useful guarantees about this. --- src/type-layout.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/type-layout.md b/src/type-layout.md index 078a90e442..90588f39fd 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -69,6 +69,40 @@ Pointers to unsized types are sized. The size and alignment of a pointer to an u > [!NOTE] > Though you should not rely on this, all pointers to DSTs are currently twice the size of the size of `usize` and have the same alignment. +r[layout.pointer.parametric] +The raw pointer types `*const T` and `*const U` have the same layout, as do `*mut T` and `*mut U`, the shared references `&T` and `&U`, and the mutable references `&mut T` and `&mut U`, in each of the following cases: + +- `T` and `U` are both sized types. +- `T` and `U` both have a [slice] or [`str`] as their [unsized tail]. +- `T` and `U` both have a [trait object] as their unsized tail. + +```rust +# use core::alloc::Layout; +macro_rules! assert_layout_eq { + ($a:ty, $b:ty) => { + assert_eq!(Layout::new::<$a>(), Layout::new::<$b>()) + }; +} +trait Tr1 {} +trait Tr2 {} +struct W { + head: u8, + tail: T, +} +// Pointers to two types share a layout when the types are both sized, +// both have a slice or `str` tail, or both have a trait object tail. +assert_layout_eq!(*const u8, *const u64); +assert_layout_eq!(*const [u8], *const [u64]); +assert_layout_eq!(*const str, *const [u8]); +assert_layout_eq!(*const dyn Tr1, *const dyn Tr2); +assert_layout_eq!(*const W<[u8]>, *const W<[u64]>); +// References share a layout on the same terms. +assert_layout_eq!(&u8, &u64); +# assert_layout_eq!(&str, &[u8]); +# assert_layout_eq!(&dyn Tr1, &dyn Tr2); +# assert_layout_eq!(&W<[u8]>, &W<[u64]>); +``` + r[layout.array] ## Array layout @@ -571,3 +605,7 @@ Because this representation delegates type layout to another type, it cannot be [structs]: items/structs.md [`transparent`]: #the-transparent-representation [`Layout`]: std::alloc::Layout +[slice]: types/slice.md +[`str`]: types/str.md +[trait object]: types/trait-object.md +[unsized tail]: dynamic-sized.tail From e57f8614ccf65e312ad04a18cc789d892b496bba Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 1 Jun 2026 17:09:57 +0000 Subject: [PATCH 4/6] Document transmutability of pointer types Our rules for `as` casts document which pointer-to-pointer casts are valid. For unsized types, this validity is based on the compatibility of the pointer metadata. Using this and our layout equivalences, we can define when a pointer transmute will produce the same pointer value as would a cast. Let's define that. --- src/types/pointer.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/types/pointer.md b/src/types/pointer.md index ffd234a3b7..10113d0a38 100644 --- a/src/types/pointer.md +++ b/src/types/pointer.md @@ -63,6 +63,23 @@ r[type.pointer.smart] The standard library contains additional 'smart pointer' types beyond references and raw pointers. +r[type.pointer.transmute] +## Transmutation + +r[type.pointer.transmute.cast] +When `*const T` and `*const U` have the same layout (per [layout.pointer.parametric]), transmuting a `*const T` to a `*const U` reinterprets the pointer value --- its address and [metadata] --- unchanged. When `T` and `U` are both sized or both have a slice or `str` as their [unsized tail], this produces the same value as a [pointer-to-pointer cast] from `*const T` to `*const U` (where such a cast is permitted), since the cast copies the metadata unchanged. The same holds between `*mut T` and `*mut U`. Transmuting between the reference types `&T` and `&U`, or between `&mut T` and `&mut U`, reinterprets the pointer value the same way, but is sound only when the result is aligned for the target type and points to a valid value of it, as required by [undefined.validity.reference-box] and [undefined.validity.wide]. + +```rust +let ptr: *const i8 = &1; +let cast = ptr as *const u8; +// Transmuting this pointer is equivalent to casting it with `as`. +let transmuted = unsafe { *(&raw const ptr as *const *const u8) }; +assert_eq!(cast, transmuted); +``` + +> [!NOTE] +> This equivalence with the cast does not extend to trait objects. The conversions permitted for `as` casts between trait-object pointers --- adding or removing an auto trait, and a [trait object upcast] (converting `*const dyn Sub` to `*const dyn Super` when `Super` is a supertrait of `Sub`) --- are [unsized coercions][unsized coercion], not pointer-to-pointer casts. The conversion builds a vtable for the target type, whereas a transmute keeps the source vtable. For an auto-trait change the kept vtable is still valid, so the transmute is sound but does not necessarily produce the same pointer value as the cast. For an upcast, the kept vtable is `Sub`'s, which is not valid for `Super`, so the transmuted pointer is invalid. + r[type.pointer.validity] ## Bit validity @@ -75,5 +92,10 @@ For thin raw pointers (i.e., for `P = *const T` or `P = *mut T` for `T: Sized`), [Interior mutability]: ../interior-mutability.md [`unsafe` operation]: ../unsafety.md [dynamically sized types]: ../dynamically-sized-types.md +[metadata]: dynamic-sized.pointer-types +[pointer-to-pointer cast]: expr.as.pointer [size zero]: glossary.zst +[unsized tail]: dynamic-sized.tail [temporary value]: ../expressions.md#temporaries +[trait object upcast]: coerce.unsize.trait-upcast +[unsized coercion]: coerce.unsize From 4f2f8445a2c55da7db35df7fa42ae70d025b7ad0 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 1 Jun 2026 17:09:57 +0000 Subject: [PATCH 5/6] Document when `Box` to `Box` transmute is sound We hadn't documented when it's sound to transmute a `Box` to a `Box`. Now that we've documented when `*mut T` and `*mut U` can be assumed to have the same layout, let's use that to document when this transmute is sound. --- src/special-types-and-traits.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/special-types-and-traits.md b/src/special-types-and-traits.md index dddefda040..57986ebeba 100644 --- a/src/special-types-and-traits.md +++ b/src/special-types-and-traits.md @@ -21,6 +21,33 @@ r[lang-types.box.fundamental] +r[lang-types.box.transmute] +For types `T` and `U` such that `*mut T` and `*mut U` have the same layout (per [layout.pointer.parametric]) and a [pointer-to-pointer cast] from `*mut T` to `*mut U` is permitted, transmuting a `Box` to a `Box`, where both boxes use the global allocator, is sound when both of the following hold: + +- The pointee has the same [size] and [alignment] whether viewed as `T` or as `U`. +- The pointee is a valid value of `U`. + +```rust +# use core::mem::transmute; +let boxed: Box = Box::new(1); +let addr = (&raw const *boxed).addr(); +// SAFETY: `i8` and `u8` have the same size and the same alignment, so +// `boxed`'s allocation is valid for `u8`. +let reboxed: Box = unsafe { transmute(boxed) }; +// The transmute reuses the same allocation. +assert_eq!((&raw const *reboxed).addr(), addr); +assert_eq!(*reboxed, 1); + +// The pointee may also be unsized. +assert_eq!(size_of::(), size_of::()); +assert_eq!(align_of::(), align_of::()); +let slice: Box<[u32]> = Box::new([1, 2, 3]); +// SAFETY: Every bit pattern is a valid `f32`, and transmuting the box +// preserves the slice's length. +let floats: Box<[f32]> = unsafe { transmute(slice) }; +assert_eq!(floats.len(), 3); +``` + r[lang-types.rc] ## `Rc` @@ -186,6 +213,7 @@ These implicit `Sized` bounds may be relaxed by using the special `?Sized` bound [`UnwindSafe`]: std::panic::UnwindSafe [`Unpin`]: std::marker::Unpin +[alignment]: layout.properties.align [Arrays]: types/array.md [associated types]: items/associated-items.md#associated-types [call expressions]: expressions/call-expr.md @@ -205,6 +233,8 @@ These implicit `Sized` bounds may be relaxed by using the special `?Sized` bound [moved from]: expr.move.movable-place [operators]: expressions/operator-expr.md [orphan rules]: items/implementations.md#trait-implementation-coherence +[pointer-to-pointer cast]: expr.as.pointer +[size]: layout.properties.size [`static` items]: items/static-items.md [test functions]: attributes/testing.md#the-test-attribute [the standard library]: std From 6ca17b8fc1f17a3e6d8b6635d2a3a2f2609d77be Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 9 Jun 2026 21:09:42 +0000 Subject: [PATCH 6/6] Cover `str` in the wide-pointer metadata rule The validity rule for the metadata of a wide reference, `Box`, or raw pointer mentions `dyn Trait` and slice but had omitted `str`. Let's fix that. --- src/behavior-considered-undefined.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index 94783af9ec..f90bc45f40 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -147,7 +147,7 @@ r[undefined.validity.reference-box] r[undefined.validity.wide] * The [metadata] of a wide reference, [`Box`], or raw pointer must match the type of the [unsized tail]: * `dyn Trait` metadata must be a pointer to a compiler-generated vtable for `Trait`. (For raw pointers, this requirement remains a subject of some debate.) - * Slice (`[T]`) metadata must be a valid `usize`. Furthermore, for wide references and [`Box`], slice metadata is invalid if it makes the total size of the pointed-to value bigger than `isize::MAX`. + * Slice (`[T]`) and `str` metadata must be a valid `usize`. Furthermore, for wide references and [`Box`], this metadata is invalid if it makes the total size of the pointed-to value bigger than `isize::MAX`. r[undefined.validity.valid-range] * If a type has a custom range of a valid values, then a valid value must be in that range. In the standard library, this affects [`NonNull`] and [`NonZero`].