Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions src/behavior-considered-undefined.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -142,12 +142,12 @@ 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<T>`] 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<T>`] 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<T>`], or raw pointer must match the type of the unsized tail:
* The [metadata] of a wide reference, [`Box<T>`], 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<T>`], 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<T>`], 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<T>`] and [`NonZero<T>`].
Expand All @@ -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.

Expand Down Expand Up @@ -194,6 +194,7 @@ r[undefined.validity.undef]
[`target_feature`]: attributes/codegen.md#the-target_feature-attribute
[`UnsafeCell<U>`]: std::cell::UnsafeCell
[Rustonomicon]: ../nomicon/index.html
[metadata]: dynamic-sized.pointer-types
[`NonNull<T>`]: core::ptr::NonNull
[`NonZero<T>`]: core::num::NonZero
[place expression context]: expressions.md#place-expressions-and-value-expressions
Expand All @@ -203,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
Expand Down
13 changes: 10 additions & 3 deletions src/dynamically-sized-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <abbr title="dynamically sized types">DSTs</abbr> 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 <abbr title="dynamically sized types">DSTs</abbr> 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.

@Mark-Simulacrum Mark-Simulacrum Jun 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing notes with https://doc.rust-lang.org/nightly/std/ptr/trait.Pointee.html#pointer-metadata, I think the only missing piece is the "unsized / last field" indirection for indirectly unsized types.

View changes since the review


r[dynamic-sized.question-sized]
* <abbr title="dynamically sized types">DSTs</abbr> 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`.
Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/expressions/operator-expr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions src/special-types-and-traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,33 @@ r[lang-types.box.fundamental]

<!-- Editor Note: This is nowhere close to an exhaustive list -->

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<T>` to a `Box<U>`, where both boxes use the global allocator, is sound when both of the following hold:

@ehuss ehuss Jun 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, why "global" here?

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Box with a custom allocator is not necessarily going to put the allocator in the same place -- e.g. under -Zrandomize-layout -- for different value types, so I don't think we can make this guarantee. Maybe it can be made for 1-ZST allocators? Not sure...


- 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<i8> = 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<u8> = 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::<u32>(), size_of::<f32>());
assert_eq!(align_of::<u32>(), align_of::<f32>());
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<T>`

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
38 changes: 38 additions & 0 deletions src/type-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <abbr title="Dynamically Sized Types">DSTs</abbr> 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<T: ?Sized> {
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.

@Mark-Simulacrum Mark-Simulacrum Jun 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I guess the pointers would share a layout even under -Zrandomize-layout? Though that may not guarantee transmutes are sound (since alignment could change).

View changes since the review

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

Expand Down Expand Up @@ -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
22 changes: 22 additions & 0 deletions src/types/pointer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Loading