Structs in Rust
Grouping related data into custom types with structs, and the shorthand syntax that keeps them readable.
2 min read
A struct groups related values into a single named type — Rust's equivalent of a class's data (without inheritance, which Rust deliberately doesn't have).
Defining and instantiating a struct
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
fn main() {
let user1 = User {
username: String::from("ada"),
email: String::from("ada@example.com"),
active: true,
sign_in_count: 1,
};
println!("{}", user1.username);
}Fields are accessed with dot notation. To change a field, the whole instance must be marked mut — Rust doesn't allow marking individual fields as mutable.
let mut user1 = User { /* ... */ };
user1.email = String::from("new_email@example.com");Field init shorthand
When a variable name matches a field name exactly, you can skip repeating it:
fn build_user(email: String, username: String) -> User {
User {
email, // instead of email: email
username, // instead of username: username
active: true,
sign_in_count: 1,
}
}Struct update syntax
To build a new instance that reuses most of another instance's values, use ..:
fn main() {
let user1 = User {
username: String::from("ada"),
email: String::from("ada@example.com"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("different@example.com"),
..user1 // fill remaining fields from user1
};
}Watch out: any field copied from user1 that doesn't implement Copy (like username, a String) is moved into user2 — user1 as a whole becomes only partially usable afterward, since user1.username was moved out.
Tuple structs and unit structs
A tuple struct has a name but unnamed fields, useful for giving a plain tuple type-level meaning:
struct Point(f64, f64, f64);
fn main() {
let origin = Point(0.0, 0.0, 0.0);
println!("{}", origin.0);
}Point(0.0, 0.0, 0.0) and a plain (f64, f64, f64) tuple hold identical data, but Point can't accidentally be passed where a different tuple struct like Color(f64, f64, f64) is expected — the compiler treats them as distinct types even though their internal shape matches.
A unit struct has no fields at all, useful when you need a type to implement a trait but don't need to store any data on it:
struct AlwaysEqual;Printing a struct for debugging
By default, println!("{}", user1) won't compile — structs have no default display format. Derive Debug and use {:?} (or {:#?} for pretty-printed output) instead:
#[derive(Debug)]
struct User {
username: String,
active: bool,
}
fn main() {
let user1 = User { username: String::from("ada"), active: true };
println!("{:?}", user1);
println!("{:#?}", user1);
}#[derive(Debug)] is an attribute that asks the compiler to auto-generate a Debug implementation — you'll see #[derive(...)] constantly in idiomatic Rust, and it's usually the first thing to add to a new struct while you're still developing.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.