diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index a99ae38850..f90bc45f40 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,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`] 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`. + * 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`]. @@ -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 @@ -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 diff --git a/src/dynamically-sized-types.md b/src/dynamically-sized-types.md index cd90adb75d..b14a807e83 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`. @@ -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 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 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 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 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