From 93776bbb98e9f031b8b5e6b70d8b1af62f47c21f Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 10 Mar 2026 12:51:21 -0700 Subject: [PATCH 1/2] Glossary: add new entry documenting zero-sized types --- src/glossary.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/glossary.md b/src/glossary.md index 9d39173e01..3e04cd148a 100644 --- a/src/glossary.md +++ b/src/glossary.md @@ -211,9 +211,68 @@ r[glossary.uninhabited] A type is uninhabited if it has no constructors and therefore can never be instantiated. An uninhabited type is "empty" in the sense that there are no values of the type. The canonical example of an uninhabited type is the [never type] `!`, or an enum with no variants `enum Never { }`. Opposite of [Inhabited](#inhabited). +r[glossary.zst] +### Zero-sized type (ZST) + +A type is zero sized (a ZST) if its size is 0. Such types have at most one possible value. Examples include: + +- The [unit type] (see [layout.tuple.unit]). +- [Function items] (see [type.fn-item.intro]). +- The constructors of tuple-like structs (see [type.fn-item.intro]). +- The constructors of tuple-like enum variants (see [type.fn-item.intro]). +- `#[repr(C)]` structs with no fields ([unit-like structs]) or where all fields are zero-sized (see [layout.repr.c.struct.size-field-offset]). +- `#[repr(transparent)]` structs with no fields ([unit-like structs]) or where all fields are zero-sized (see [layout.repr.transparent.layout-abi]). +- [Arrays] of zero-sized types (see [layout.array]). +- [Arrays] of length zero (see [layout.array]). +- [Unions] of zero-sized types (see [items.union.common-storage]). + +```rust +# use core::mem::{size_of, size_of_val}; +fn f() {} +struct S(u8); +enum E{ V(u8) } +#[repr(C)] +struct C1 {} +#[repr(C)] +struct C2 { + f1: (), + f2: [(); 10], + f3: [u8; 0], + f4: C1, +} +#[repr(transparent)] +struct T1 {} +#[repr(transparent)] +struct T2 { + f1: (), + f2: [(); 10], + f3: [u8; 0], +} +union U { + f1: (), + f2: [(); 10], + f3: [u8; 0], +} +assert_eq!(0, size_of::<()>()); +assert_eq!(0, size_of_val(&f)); +// Note that here we are checking the size of the constructors, *not* the +// underlying type, for `S` and `E::V`. The constructors just have the same +// names as the types. +assert_eq!(0, size_of_val(&S)); +assert_eq!(0, size_of_val(&E::V)); +assert_eq!(0, size_of::()); +assert_eq!(0, size_of::()); +assert_eq!(0, size_of::()); +assert_eq!(0, size_of::()); +assert_eq!(0, size_of::<[(); 10]>()); +assert_eq!(0, size_of::<[u8; 0]>()); +assert_eq!(0, size_of::()); +``` + [`extern` blocks]: items.extern [`extern fn`]: items.fn.extern [alignment]: type-layout.md#size-and-alignment +[arrays]: type.array [associated item]: #associated-item [attributes]: attributes.md [*entity*]: names.md @@ -222,6 +281,7 @@ A type is uninhabited if it has no constructors and therefore can never be insta [enums]: items/enumerations.md [fields]: expressions/field-expr.md [free item]: #free-item +[function items]: type.fn-item [generic parameters]: items/generics.md [identifier]: identifiers.md [identifiers]: identifiers.md @@ -252,5 +312,7 @@ A type is uninhabited if it has no constructors and therefore can never be insta [types]: types.md [undefined-behavior]: behavior-considered-undefined.md [unions]: items/unions.md +[unit type]: type.tuple.unit +[unit-like structs]: items.struct.unit [variable bindings]: patterns.md [visibility rules]: visibility-and-privacy.md From dc4cf6551d0db53f997b544da781af63b138f3d0 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Tue, 28 Apr 2026 11:47:34 -0700 Subject: [PATCH 2/2] Add backlinks to `glossary.zst` --- src/behavior-considered-undefined.md | 5 +++-- src/items/functions.md | 3 ++- src/special-types-and-traits.md | 3 ++- src/type-layout.md | 12 ++++++++---- src/types/function-item.md | 3 ++- src/types/pointer.md | 3 ++- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/behavior-considered-undefined.md b/src/behavior-considered-undefined.md index cd85a03255..373052061c 100644 --- a/src/behavior-considered-undefined.md +++ b/src/behavior-considered-undefined.md @@ -139,7 +139,7 @@ A reference/pointer is "dangling" if not all of the bytes it part of *some* allocation). r[undefined.dangling.zero-size] -If the size is 0, then the pointer is trivially never "dangling" +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] @@ -189,7 +189,7 @@ r[undefined.validity.struct] 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. + 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] @@ -268,3 +268,4 @@ reading uninitialized memory is permitted are inside `union`s and in "padding" [unwinding-ffi]: panic.md#unwinding-across-ffi-boundaries [const-promoted]: destructors.md#constant-promotion [lifetime-extended]: destructors.md#temporary-lifetime-extension +[zero-sized]: glossary.zst diff --git a/src/items/functions.md b/src/items/functions.md index 1586adc33c..362b39b573 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -53,7 +53,7 @@ r[items.fn.implicit-return] If the output type is not explicitly stated, it is the [unit type]. r[items.fn.fn-item-type] -When referred to, a _function_ yields a first-class *value* of the corresponding zero-sized [*function item type*], which when called evaluates to a direct call to the function. +When referred to, a _function_ yields a first-class *value* of the corresponding [zero-sized] [*function item type*], which when called evaluates to a direct call to the function. For example, this is a simple function: @@ -426,3 +426,4 @@ fn foo_oof(#[some_inert_attribute] arg: u8) { [value namespace]: ../names/namespaces.md [variadic function]: external-blocks.md#variadic-functions [`extern` block]: external-blocks.md +[zero-sized]: glossary.zst diff --git a/src/special-types-and-traits.md b/src/special-types-and-traits.md index 429ff9df1c..9f6806ca8a 100644 --- a/src/special-types-and-traits.md +++ b/src/special-types-and-traits.md @@ -59,7 +59,7 @@ mutability aren't placed in memory marked as read only. r[lang-types.phantom-data] ## `PhantomData` -[`std::marker::PhantomData`] is a zero-sized, minimum alignment, type that +[`std::marker::PhantomData`] is a [zero-sized], minimum alignment, type that is considered to own a `T` for the purposes of [variance], [drop check], and [auto traits](#auto-traits). @@ -246,6 +246,7 @@ These implicit `Sized` bounds may be relaxed by using the special `?Sized` bound [test functions]: attributes/testing.md#the-test-attribute [the standard library]: std [trait object]: types/trait-object.md +[zero-sized]: glossary.zst [Tuples]: types/tuple.md [Type parameters]: types/parameters.md [variance]: subtyping.md#variance diff --git a/src/type-layout.md b/src/type-layout.md index 6d25fbb4f1..c3933e193b 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -18,7 +18,7 @@ r[layout.properties.align] The *alignment* of a value specifies what addresses are valid to store the value at. A value of alignment `n` must only be stored at an address that is a multiple of n. For example, a value with an alignment of 2 must be stored at an even address, while a value with an alignment of 1 can be stored at any address. Alignment is measured in bytes, and must be at least 1, and always a power of 2. The alignment of a value can be checked with the [`align_of_val`] function. r[layout.properties.size] -The *size* of a value is the offset in bytes between successive elements in an array with that item type including alignment padding. The size of a value is always a multiple of its alignment. Note that some types are zero-sized; 0 is considered a multiple of any alignment (for example, on some platforms, the type `[u16; 0]` has size 0 and alignment 2). The size of a value can be checked with the [`size_of_val`] function. +The *size* of a value is the offset in bytes between successive elements in an array with that item type including alignment padding. The size of a value is always a multiple of its alignment. Note that some types are [zero-sized]; 0 is considered a multiple of any alignment (for example, on some platforms, the type `[u16; 0]` has size 0 and alignment 2). The size of a value can be checked with the [`size_of_val`] function. r[layout.properties.sized] Types where all values have the same size and alignment, and both are known at compile time, implement the [`Sized`] trait and can be checked with the [`size_of`] and [`align_of`] functions. Types that are not [`Sized`] are known as [dynamically sized types]. Since all values of a `Sized` type share the same size and alignment, we refer to those shared values as the size of the type and the alignment of the type respectively. @@ -94,7 +94,7 @@ r[layout.tuple.def] Tuples are laid out according to the [`Rust` representation][`Rust`]. r[layout.tuple.unit] -The exception to this is the unit tuple (`()`), which is guaranteed as a zero-sized type to have a size of 0 and an alignment of 1. +The exception to this is the unit tuple (`()`), which is guaranteed as a [zero-sized type] to have a size of 0 and an alignment of 1. r[layout.trait-object] ## Trait object layout @@ -178,7 +178,7 @@ The only data layout guarantees made by this representation are those required f r[layout.repr.rust.layout.struct] For [structs], it is further guaranteed that the fields do not overlap. That is, the fields can be ordered such that the offset plus the size of any field is less than or equal to the offset of the next field in the ordering. The ordering does not have to be the same as the order in which the fields are specified in the declaration of the type. -Be aware that this guarantee does not imply that the fields have distinct addresses: zero-sized types may have the same address as other fields in the same struct. +Be aware that this guarantee does not imply that the fields have distinct addresses: [zero-sized types] may have the same address as other fields in the same struct. r[layout.repr.rust.unspecified] There are no other guarantees of data layout made by this representation. @@ -248,7 +248,7 @@ struct.size = current_offset + padding_needed_for(current_offset, struct.alignme > This pseudocode uses a naive algorithm that ignores overflow issues for the sake of clarity. To perform memory layout computations in actual code, use [`Layout`]. > [!NOTE] -> This algorithm can produce zero-sized structs. In C, an empty struct declaration like `struct Foo { }` is illegal. However, both gcc and clang support options to enable such structs, and assign them size zero. C++, in contrast, gives empty structs a size of 1, unless they are inherited from or they are fields that have the `[[no_unique_address]]` attribute, in which case they do not increase the overall size of the struct. +> This algorithm can produce [zero-sized structs]. In C, an empty struct declaration like `struct Foo { }` is illegal. However, both gcc and clang support options to enable such structs, and assign them size zero. C++, in contrast, gives empty structs a size of 1, unless they are inherited from or they are fields that have the `[[no_unique_address]]` attribute, in which case they do not increase the overall size of the struct. r[layout.repr.c.union] #### `#[repr(C)]` Unions @@ -561,6 +561,10 @@ Because this representation delegates type layout to another type, it cannot be [enumerations]: items/enumerations.md [zero-variant enums]: items/enumerations.md#zero-variant-enums [undefined behavior]: behavior-considered-undefined.md +[zero-sized]: glossary.zst +[zero-sized type]: glossary.zst +[zero-sized types]: glossary.zst +[zero-sized structs]: glossary.zst [`PhantomData`]: special-types-and-traits.md#phantomdatat [`Rust`]: #the-rust-representation [`C`]: #the-c-representation diff --git a/src/types/function-item.md b/src/types/function-item.md index ca70d8f090..1be2efa156 100644 --- a/src/types/function-item.md +++ b/src/types/function-item.md @@ -2,7 +2,7 @@ r[type.fn-item] # Function item types r[type.fn-item.intro] -When referred to, a function item, or the constructor of a tuple-like struct or enum variant, yields a zero-sized value of its _function item type_. +When referred to, a function item, or the constructor of a tuple-like struct or enum variant, yields a [zero-sized] value of its _function item type_. r[type.fn-item.unique] That type explicitly identifies the function - its name, its type arguments, and its early-bound lifetime arguments (but not its late-bound lifetime arguments, which are only assigned when the function is called) - so the value does not need to contain an actual function pointer, and no indirection is needed when the function is called. @@ -51,3 +51,4 @@ All function items implement [`Copy`], [`Clone`], [`Send`], and [`Sync`]. [`Sync`]: ../special-types-and-traits.md#sync [coercion]: ../type-coercions.md [function pointers]: function-pointer.md +[zero-sized]: glossary.zst diff --git a/src/types/pointer.md b/src/types/pointer.md index 91a94c1e95..ffd234a3b7 100644 --- a/src/types/pointer.md +++ b/src/types/pointer.md @@ -70,9 +70,10 @@ r[type.pointer.validity.pointer-fragment] Despite pointers and references being similar to `usize`s in the machine code emitted on most platforms, the semantics of transmuting a reference or pointer type to a non-pointer type is currently undecided. Thus, it may not be valid to transmute a pointer or reference type, `P`, to a `[u8; size_of::

()]`. r[type.pointer.validity.raw] -For thin raw pointers (i.e., for `P = *const T` or `P = *mut T` for `T: Sized`), the inverse direction (transmuting from an integer or array of integers to `P`) is always valid. However, the pointer produced via such a transmutation may not be dereferenced (not even if `T` has size zero). +For thin raw pointers (i.e., for `P = *const T` or `P = *mut T` for `T: Sized`), the inverse direction (transmuting from an integer or array of integers to `P`) is always valid. However, the pointer produced via such a transmutation may not be dereferenced (not even if `T` has [size zero]). [Interior mutability]: ../interior-mutability.md [`unsafe` operation]: ../unsafety.md [dynamically sized types]: ../dynamically-sized-types.md +[size zero]: glossary.zst [temporary value]: ../expressions.md#temporaries