Structs, enums and unions are Rust’s user-defined types. All three are
items, can take
generic parameters and get methods through
impl blocks. Their
memory layout is unspecified unless a
repr attribute says otherwise
(Reference: Structs, Enumerations, Unions).
Structs
Struct: StructStruct | TupleStruct
StructStruct:
struct IDENTIFIER GenericParams? WhereClause? ( { StructFields? } | ; )
TupleStruct:
struct IDENTIFIER GenericParams? ( TupleFields? ) WhereClause? ;
StructFields: StructField ( , StructField )* ,?
StructField: OuterAttribute* Visibility? IDENTIFIER : Type
TupleFields: TupleField ( , TupleField )* ,?
TupleField: OuterAttribute* Visibility? Type
| Form | Example | Name binding |
|---|---|---|
| Named fields | struct Point { x: i32, y: i32 } | The type namespace only |
| Tuple struct | struct Point(i32, i32); | Type namespace, plus a constructor function in the value namespace |
| Unit-like struct | struct Cookie; | Type namespace, plus a constant of the type in the value namespace |
The name is defined in the enclosing module or block. Fields are private by default;
each may have its own visibility and outer attributes. The compiler chooses the field
order and padding unless a repr attribute fixes them.
A tuple struct must end in ;, with any where clause after the
fields. struct Cookie; is equivalent to struct Cookie {}
plus a constant named Cookie.
Creating and reading values
struct Point { x: i32, y: i32 }
let p = Point { x: 10, y: 20 }; // named fields, any order
let x = 5;
let q = Point { x, y: 1 }; // shorthand for x: x
let r = Point { x: 7, ..p }; // takes the remaining fields from p
println!("{}", p.x);
struct Pair(i32, i32);
let a = Pair(1, 2); // the tuple constructor
let b = Pair { 0: 1, 1: 2 }; // brace syntax with numeric field names
let f = Pair; // the constructor is a function value
let c = f(3, 4);
println!("{}", a.0);
struct Cookie;
let c1 = Cookie;
let c2 = Cookie {}; // both forms create the value
- Every field must be given, unless
..basesupplies the rest. ..base, called functional record update, moves or copies only the fields that are not listed, and needs an expression of the same type. All fields of the struct must be visible to the code using it.- A struct expression may not be the condition of an
if,whileormatchwithout parentheses, because the brace would be read as the start of the body.
Enums
Enumeration:
enum IDENTIFIER GenericParams? WhereClause? { EnumVariants? }
EnumVariants:
EnumVariant ( , EnumVariant )* ,?
EnumVariant:
OuterAttribute* Visibility? IDENTIFIER
( ( TupleFields? ) | { StructFields? } )? ( = Expression )?
An enum defines a type and a set of constructors, called variants. A value is exactly one variant. Each variant has a type in the type namespace, and tuple-like and unit-like variants also have a constructor in the value namespace.
enum Message {
Quit, // unit-like
Write(String), // tuple-like
Move { x: i32, y: i32 }, // struct-like
}
let m = Message::Move { x: 1, y: 2 };
match m {
Message::Quit => {}
Message::Write(text) => println!("{text}"),
Message::Move { x, y } => println!("{x} {y}"),
}
Variants take the visibility of the enum. A visibility written on a variant is accepted
by the parser but rejected afterwards, so that macros can handle items uniformly.
The standard library’s Option<T> and Result<T, E> are
ordinary enums (see Error handling).
Discriminants
Each enum value has a discriminant, an integer that says which variant it is. Under
the default representation it is treated as an isize, although the
compiler may store it in less space.
- Implicit discriminants start at 0 and count up from the previous variant.
- A variant can have an explicit discriminant only if the enum is unit-only, or if
it uses a primitive representation such as
#[repr(u8)]. - Two variants may not share a discriminant. An implicit discriminant may not go past the maximum of its representation. An explicit discriminant cannot use the enum’s generic parameters.
std::mem::discriminantreturns an opaque value that can be compared but not converted to an integer.
enum Foo {
Bar, // 0
Baz = 123, // 123
Quux, // 124
}
assert_eq!(Foo::Baz as u32, 123);
#[repr(u8)]
enum Status { Ok = 200, Busy = 201, Failed = 255 } // each discriminant must fit in u8
A as cast to an integer type is allowed for unit-only enums, and for
field-less enums whose explicit discriminants are only on unit variants. It is not
allowed if the enum implements Drop. An enum with a primitive
representation has its discriminant stored as a value of that primitive, which can
be read by reading through a pointer to the enum.
Zero-variant enums
enum Void {} has no values and can never be created. It is like the
never type !, except that it
does not coerce to other types.
Layout and null-pointer optimisation
The layout of an enum with no repr is not specified, and the compiler
may use unused bit patterns of a variant’s fields to store the discriminant. The
standard library guarantees that Option<T> has the same size as
T for &T, &mut T, Box<T>,
NonNull<T>, function pointers and the NonZero integers,
with None being the all-zero value. For the layout of enums with a
repr attribute, see Types.
Unions
Union:
union IDENTIFIER GenericParams? WhereClause? { StructFields? }
A union looks like a struct, but all its fields share the same storage. Writing one field can overwrite the others, and the size of the union is determined by its largest field. At least one field is required. There is no record of which field was last written.
#[repr(C)]
union Value {
i: i32,
f: f32,
}
let mut v = Value { i: 1 }; // exactly one field is initialised
v.f = 2.5; // writing is safe
let f = unsafe { v.f }; // reading is unsafe
| Topic | Rule |
|---|---|
| Field types | Copy types, references, ManuallyDrop<T>, and tuples and arrays of those. Because a field never needs dropping, a write to a field never drops anything. The union may still implement Drop itself |
| Creation | Struct-like syntax with exactly one field |
| Reading | Requires unsafe. The bits are read as the field’s type. They must be valid for that type: reading 3 as a bool is undefined behaviour |
| Writing | Safe |
| Field offsets | Fields may be at non-zero offsets unless #[repr(C)] is used |
| Pattern matching | Struct-pattern syntax naming exactly one field, inside unsafe. The order of subpattern tests is unspecified, so do not rely on other parts of a pattern to guard a read |
| References | Borrowing one field borrows the whole union for the same lifetime |
Apart from layout, safety and ownership, unions behave like structs, including privacy, generics, name resolution, implementations and coherence. A union with a tag, common in C, is modelled by a struct holding an enum tag and a union, with the tag matched first:
#[repr(u32)]
enum Tag { I, F }
#[repr(C)]
union U { i: i32, f: f32 }
#[repr(C)]
struct Value { tag: Tag, u: U }
fn is_zero(v: Value) -> bool {
match v.tag {
Tag::I => unsafe { v.u.i == 0 },
Tag::F => unsafe { v.u.f == 0.0 },
}
}