In Rust, an entire instance of a struct must be mutable; the language doesn't permit marking only specific fields as mutable. By constructing a new instance of the struct as the final expression in the function body, you can implicitly return that new instance.
A struct
in Rust is a custom data type that enables you to group and name multiple related values, forming a coherent package.
There are three types of structs:
Structs with Named Fields: These are typical object declarations with key-value pairs. If you wish to change a property value of a struct, its instance must be mutable. The "field init shorthand" is a convenient feature allowing you to set a field value with a single word when the variable names match:
// Not recommended User { name: name, } // Recommended User { name, }
The "update syntax" lets you borrow values from another struct instance when creating a new one. This can simplify initialization of new struct instances by borrowing from existing ones. For fields that don't implement the
Copy
trait, their values will be moved, rendering the old instance unusable:let new_user = User { email: "new_user@ema.il", ..old_user }
Tuple Structs: These are structs without named fields. You access the fields using their index, starting from 0. Despite having the same field types, each tuple struct is treated as a unique type:
struct RgbColor(i32, i32, i32); struct Location(u32, u32);
Unit-like Structs: These are structs with no fields and can be particularly useful for traits or type definitions:
struct PoundsOfForce(f64); struct AlwaysEqual; fn handle_unit_like() { let subject = AlwaysEqual; }
Struct Data Ownership
Structs can store both owned types (like String
) and reference types (&str
). When storing reference types, you must define their lifetimes, ensuring the data remains valid as long as the struct
exits.
Methods and Associated Functions
Methods are introduced within an impl
block, which can be separate from the struct declaration. A method can either take ownership of self
, borrow self
immutably, or borrow self
mutably. Methods must include a self
parameter, which refers to the instance of the struct.
It's noteworthy that method names can match struct field names. Often, methods with the same name as a field (known as getters) are used to grant read-only access to that field, especially when the field is private.
struct Person {
name: String,
}
impl Person {
fn name(&self) -> &str {
&self.name
}
}
Rust handles method calls with automatic referencing and dereferencing. This means that when calling a method, Rust will automatically add in &
, &mut
, or *
to ensure the method signature matches the called object.
All Struct Concepts in a Code Example
rustCopy code
// Struct with named fields
struct Person {
name: String,
surname: String,
location: Position,
}
impl Person {
// In the context of this struct, "self" refers to the instance and "Self" refers to the type of the struct.
fn describe(&self) {
println!("Full Name: {} {}", self.name, self.surname);
println!("Your current location is: x: {} / y: {}", self.location.0, self.location.1);
}
// Associated functions are accessed using '::' notation.
fn new(name: String) -> Self {
Self {
name,
surname: "What".to_string(),
location: Position(1, 2, 3),
}
}
}
fn spawn(Person { location: Position(x, y, ..), name, .. }: Person) {
println!("{} spawned at {} {}", name, x, y);
}
fn main() {
let person = Person::new("art".to_string());
let female = Person::new("maria".to_string());
person.describe();
// Destructure a struct
let Person { name, location, .. } = person;
spawn(female);
println!("person {} is at {} {}", name, location.0, location.1);
todo!("I need to refactor this");
}
Reference
Best Video Material
[YouTube] 300 seconds of Rust - 10. Enums and Match
[YouTube] 300 seconds of Rust - 9. Structs