Structs

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 }) }