Defining Structs
A struct (short for structure) is a custom composite data type that packages related values together into a single meaningful unit. Unlike a tuple—where elements are accessed only by positional numbers (.0, .1)—a standard struct gives both the type itself and each of its fields an explicit name.
The Three Flavors of Structs in Rust
Rust supports three syntactic varieties of struct declarations:
| Struct Variety | Declaration Syntax | Trailing ;? |
When to Use |
|---|---|---|---|
| 1. Named-Field Struct | struct User { name: String, active: bool } |
No | Standard domain models, configurations, and multi-field records |
| 2. Tuple Struct | struct Color(u8, u8, u8); |
Yes | Single-field newtype wrappers (struct Meters(f64);) or short unnamed coordinate/color tuples |
| 3. Unit-Like Struct | struct AlwaysEqual; |
Yes | Zero-sized marker types, trait implementations with no state, or phantom state markers |
Named-Field Struct: Tuple Struct: Unit-Like Struct:
struct Server { struct Port(u16); struct Connected;
host: String, (Fields accessed via (Occupies 0 bytes
port: u16, .0, .1, ... or in memory!)
} destructuring)
Declaring and Instantiating a Struct
Struct names use UpperCamelCase, and field names use snake_case. Every field in a struct definition must have an explicit : Type annotation (Rust never infers field types in a struct definition):
// 1. Named-field struct definition
struct Package {
name: String,
version_major: u32,
version_minor: u32,
published: bool,
}
// 2. Tuple struct definition
struct Kilograms(f64);
// 3. Unit-like struct definition
struct VerifiedMarker;
fn main() {
let pkg = Package {
name: String::from("serde"),
version_major: 1,
version_minor: 0,
published: true,
};
let weight = Kilograms(2.5);
let _marker = VerifiedMarker;
println!(
"Package: {} v{}.{} (published: {}, weight: {} kg)",
pkg.name, pkg.version_major, pkg.version_minor, pkg.published, weight.0
);
}
Package: serde v1.0 (published: true, weight: 2.5 kg)
Key Rules of Struct Definitions
1. No Default Field Values in the struct Declaration
Unlike C++, Java, or Python dataclasses, a struct definition in Rust specifies only field names and types—you cannot write field: i32 = 10 inside struct Foo { ... } on stable Rust. Instead, default values are provided by implementing the Default trait (#[derive(Default)]) or writing a Type::new() constructor function.
2. Every Field Must Be Initialized at Construction
When constructing an instance of a struct (Package { ... }), you can list the fields in any order, but every single field declared in the struct must be provided. Omitting a field fails at compile time with E0063:
// ❌ does not compile
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 10 };
}
error[E0063]: missing field `y` in initializer of `Point`
--> src/main.rs:7:13
|
7 | let p = Point { x: 10 };
| ^^^^^ missing `y`
error: aborting due to 1 previous error
3. Recursive Struct Definitions Require Indirection (E0072)
Because rustc must know the exact compile-time byte size (size_of::<T>()) of every struct to allocate it on the stack, a struct cannot directly contain an instance of itself by value (which would have an infinite size!).
If a struct needs to refer to another node of the same type (such as a linked list or binary tree node), wrap the recursive field in a pointer type of known fixed size—such as Option<Box<Node>> or Vec<Node>:
// ❌ does not compile
struct InfiniteNode {
value: i32,
next: InfiniteNode,
}
fn main() {}
error[E0072]: recursive type `InfiniteNode` has infinite size
--> src/main.rs:1:1
|
1 | struct InfiniteNode {
| ^^^^^^^^^^^^^^^^^^^
2 | value: i32,
3 | next: InfiniteNode,
| ------------ recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
3 | next: Box<InfiniteNode>,
| ++++ +
error: aborting due to 1 previous error
Wrapping the child node in Option<Box<Node>> gives Node a finite, known size (16 bytes on 64-bit systems):
#[derive(Debug)]
struct Node {
value: i32,
next: Option<Box<Node>>,
}
fn main() {
let list = Node {
value: 1,
next: Some(Box::new(Node {
value: 2,
next: None,
})),
};
println!("List: {list:?}");
}
List: Node { value: 1, next: Some(Node { value: 2, next: None }) }