Named-Field Structs
A named-field struct (often called a C-style struct or standard struct) is the most common struct variety in Rust. It defines a composite type whose fields each have an explicit identifier and type enclosed in curly braces { field_name: Type, ... }.
Syntax and Field Naming
Every field in a named-field struct is written in snake_case followed by a colon (:) and its type, separated by commas (with a trailing comma after the last field when written across multiple lines):
#[derive(Debug)]
struct ServerEndpoint {
hostname: String,
port: u16,
tls_enabled: bool,
max_connections: usize,
}
fn main() {
let primary = ServerEndpoint {
hostname: String::from("api.example.com"),
port: 443,
tls_enabled: true,
max_connections: 1024,
};
println!(
"Connecting to {}:{} (TLS: {}, max: {})",
primary.hostname, primary.port, primary.tls_enabled, primary.max_connections
);
}
Connecting to api.example.com:443 (TLS: true, max: 1024)
Notice that a named-field struct declaration ends with its closing curly brace }—it never takes a trailing semicolon after }.
Why Named Fields Prevent Positional Argument Bugs
When a record has two or more fields of the same primitive type—such as width: u32 and height: u32, or latitude: f64 and longitude: f64—a tuple (u32, u32) makes it easy to accidentally swap the order of arguments ((height, width)).
A named-field struct eliminates positional mistakes because every field is labeled by name at both construction and access sites, and fields can be initialized in any order:
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
// Field order in the initializer does not have to match declaration order:
let screen = Rectangle {
height: 1080,
width: 1920,
};
println!("Resolution: {}x{}", screen.width, screen.height);
}
Resolution: 1920x1080
Memory Representation and Field Reordering (#[repr(Rust)])
In C (struct), the compiler is forced to lay out struct fields in memory in the exact order they are written in source code, which can waste memory on alignment padding when 1-byte and 8-byte fields alternate.
By default, Rust uses the repr(Rust) representation, which allows rustc to automatically reorder struct fields in memory to minimize alignment padding:
Source Declaration:
struct Mixed {
a: u8, (1 byte, align 1)
b: u64, (8 bytes, align 8)
c: u8, (1 byte, align 1)
}
In C (Fixed Field Order -> 24 bytes total!):
+----+-----------------------+----------------------------------+----+-----------------------+
| a | 7 bytes wasted pad | b (8 bytes) | c | 7 bytes wasted pad |
+----+-----------------------+----------------------------------+----+-----------------------+
In Rust (Default `repr(Rust)` Reorders `b` First -> Only 16 bytes total!):
+----------------------------------+----+----+------------------+
| b (8 bytes) | a | c | 6 bytes tail pad |
+----------------------------------+----+----+------------------+
use std::mem::size_of;
struct MixedRust {
a: u8,
b: u64,
c: u8,
}
#[repr(C)]
struct MixedC {
a: u8,
b: u64,
c: u8,
}
fn main() {
println!("size_of::<MixedRust>() (optimized layout) = {} bytes", size_of::<MixedRust>());
println!("size_of::<MixedC>() (#[repr(C)] order) = {} bytes", size_of::<MixedC>());
}
size_of::<MixedRust>() (optimized layout) = 16 bytes
size_of::<MixedC>() (#[repr(C)] order) = 24 bytes
When passing a struct across a Foreign Function Interface (FFI) boundary to C code, annotate the struct with #[repr(C)] so rustc preserves C's exact field ordering and padding rules.
Destructuring and Pattern Matching on Named-Field Structs
Named-field structs can be unpacked directly in let, if let, match, and function parameters using { field1, field2 } patterns (or { field1, .. } to ignore remaining fields):
struct Account {
id: u64,
username: String,
balance_cents: i64,
}
fn describe_account(acc: &Account) {
match acc {
Account { username, balance_cents: 0, .. } => {
println!("{username} has a zero balance");
}
Account { username, balance_cents, .. } if *balance_cents < 0 => {
println!("{username} is overdrawn by {} cents", -balance_cents);
}
Account { id, username, balance_cents } => {
println!("Account #{id} ({username}): {balance_cents} cents");
}
}
}
fn main() {
let user = Account {
id: 7,
username: String::from("ferris"),
balance_cents: 2500,
};
describe_account(&user);
}
Account #7 (ferris): 2500 cents