TL;DR: A
structin Rust is a named grouping of fields, the primary way to define custom data types. Unlike C structs, Rust structs can have methods (viaimpl), implement traits, and participate fully in the ownership system. There are three kinds: named-field structs (most common), tuple structs, and unit structs. Rust has no class keyword, structs withimplblocks are the equivalent.
What Is a Struct in Rust?
A struct defines a named type by grouping fields together ; Rust's primary building block for custom data structures.
struct User {
username: String,
email: String,
age: u32,
active: bool,
}
fn main() {
let user = User {
username: String::from("alice"),
email: String::from("[email protected]"),
age: 30,
active: true,
};
println!("{} ({})", user.username, user.age);
}Fields are private to the module by default. Add pub to expose them, or (more commonly) expose behaviour through methods instead.
How Do You Add Methods to a Struct?
Methods are defined in impl blocks ; Rust's equivalent of class methods. &self borrows the instance read-only; &mut self borrows it mutably; self consumes it.
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// Associated function (constructor); called as Rectangle::new(...)
pub fn new(width: f64, height: f64) -> Self {
Self { width, height }
}
// Method; takes &self, read-only access
pub fn area(&self) -> f64 {
self.width * self.height
}
// Mutable method; takes &mut self
pub fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
}
fn main() {
let mut rect = Rectangle::new(10.0, 5.0);
println!("Area: {}", rect.area()); // 50.0
rect.scale(2.0);
println!("Area after scaling: {}", rect.area()); // 200.0
}Multiple impl blocks for the same struct are allowed, useful for organizing methods or implementing traits.
What Are the Three Kinds of Structs?
Named-field structs are most common. Tuple structs are useful for newtype wrappers. Unit structs carry no data and are used as marker types.
// 1. Named-field struct; most common
struct Point {
x: f64,
y: f64,
}
// 2. Tuple struct; fields accessed by index
struct Meters(f64);
struct Seconds(f64);
let distance = Meters(100.0);
let time = Seconds(9.58);
println!("{} m", distance.0); // 100.0
// Prevents mixing up types with same underlying type:
fn speed(d: Meters, t: Seconds) -> f64 { d.0 / t.0 }
// speed(time, distance); COMPILE ERROR: wrong order
// 3. Unit struct; no fields, often used as marker types
struct Marker;
struct AdminRole;Tuple structs (also called newtypes) are a zero-cost way to add type safety, Meters and Seconds are both f64 wrappers but the compiler treats them as different types.
How Do You Derive Common Traits?
The #[derive] attribute auto-generates trait implementations for common behaviors, printing, cloning, comparing, and serializing.
#[derive(Debug, Clone, PartialEq)]
struct Config {
host: String,
port: u16,
max_connections: u32,
}
fn main() {
let config = Config {
host: "localhost".to_string(),
port: 8080,
max_connections: 100,
};
println!("{:?}", config); // Debug print
let config2 = config.clone(); // Clone
assert_eq!(config, config2); // PartialEq comparison
}Common derives:
| Derive | What it provides |
|---|---|
Debug | {:?} formatting for debugging |
Clone | .clone() method for explicit duplication |
Copy | Implicit copy on assignment (only for small, stack types) |
PartialEq | == and != operators |
Hash | Usable as HashMap key |
serde::Serialize / Deserialize | JSON/TOML/etc. serialization |
How Do Structs Interact With Ownership?
Structs follow the same ownership rules as any other type, they are moved by default, unless they implement Copy. Fields can be partially moved out of a struct.
#[derive(Debug)]
struct Message {
from: String,
body: String,
}
fn process(msg: Message) {
println!("From: {}", msg.from);
// msg is consumed here; dropped at end of function
}
fn main() {
let msg = Message {
from: "Alice".to_string(),
body: "Hello!".to_string(),
};
process(msg);
// println!("{:?}", msg); // COMPILE ERROR: msg was moved
}Use &Message to borrow (read), &mut Message to borrow mutably, or #[derive(Clone)] + .clone() to duplicate when you need to keep ownership.
Frequently Asked Questions
No. Rust uses structs + impl blocks instead. This separates data (struct) from behaviour (impl), and there is no inheritance. Reuse is achieved through trait implementations and composition.
..other_instance fills remaining fields from another instance of the same type:
let updated = User { email: "[email protected]".to_string(), ..existing_user };Note: this moves fields from existing_user that are not Copy, so existing_user may be partially moved afterward.
Yes, but the reference must have a lifetime annotation:
struct Excerpt<'a> {
text: &'a str,
}For most cases, owning the data (using String instead of &str) is simpler and avoids lifetime annotations.
A struct is a product type, it has all of its fields at once. An enum is a sum type, it is one of its variants at a time. Use a struct when a value always has all the same fields; use an enum when a value can be in different states.
Sources
Related Glossary Terms
- Enum: The complementary type, one of several variants
- Trait: Behavior you can implement on a struct
- Ownership: How struct values are moved and borrowed
- Lifetime: Required when a struct holds references
- Const Generics: Structs often use const generics for fixed-size arrays and numeric parameters
- Module: Most real Rust code organizes related structs inside modules
Keep Reading
- Rust Ownership and Borrowing Explained: structs own their fields; ownership rules apply field by field
- Rust Generics and Traits Explained: generic structs and trait implementations
- Rust for Python Developers: Python classes vs Rust structs and impl blocks
