Rust - Structure
Rust - Structures (Structs)
Section titled “Rust - Structures (Structs)”While arrays and vectors are great for storing lists of the same type, most real-world data is more complex. A structure, or struct, is a custom data type that lets you group together and name multiple related values that make up a meaningful entity. These values, called fields, can be of different types.
Structs are the building blocks for modeling your application’s domain. For example, you can create a User struct to hold a username, email, and activity status all in one place.
Declaring and Instantiating a Struct
Section titled “Declaring and Instantiating a Struct”You define a struct using the struct keyword, followed by its name and the fields with their types inside curly braces. To use a struct, you create an instance of it by specifying concrete values for each field.
// Add this attribute to allow printing the struct for debugging.#[derive(Debug)]struct User { username: String, email: String, sign_in_count: u64, active: bool,}
fn main() { // Create an instance of the User struct. let user1 = User { email: String::from("contact@example.com"), username: String::from("exampleuser123"), active: true, sign_in_count: 1, };
// Access fields using dot notation. println!("Username: {}", user1.username);
// Print the entire struct using the debug formatter. println!("User details: {:?}", user1);}Output
Section titled “Output”Username: exampleuser123User details: User { username: "exampleuser123", email: "contact@example.com", sign_in_count: 1, active: true }Mutability and Field Updates
Section titled “Mutability and Field Updates”To modify a struct’s fields, the entire instance must be marked as mutable with mut. Rust does not allow marking individual fields as mutable.
#[derive(Debug)]struct User { username: String, email: String, sign_in_count: u64, active: bool,}
fn main() { let mut user1 = User { email: String::from("contact@example.com"), username: String::from("exampleuser123"), active: true, sign_in_count: 1, };
println!("Original email: {}", user1.email);
// Modify a field on the mutable instance. user1.email = String::from("new.email@example.com");
println!("New email: {}", user1.email);}Output
Section titled “Output”Original email: contact@example.comNew email: new.email@example.comStructs, Functions, and Ownership
Section titled “Structs, Functions, and Ownership”Understanding how structs interact with functions is key to understanding Rust’s ownership system. You can pass structs to functions by value (moving ownership) or by reference (borrowing).
Passing by Value (Move)
Section titled “Passing by Value (Move)”If you pass a struct directly, ownership is moved to the function. The original variable can no longer be used.
struct Article { title: String, author: String }
// This function takes ownership of the Article.fn process_article(article: Article) { println!("Processing article: '{}' by {}", article.title, article.author);}
fn main() { let my_article = Article { title: String::from("Understanding Ownership"), author: String::from("Rust Coder") };
process_article(my_article); // Ownership is moved here.
// The following line would cause a compile error because `my_article` has been moved. // println!("Title: {}", my_article.title);}Passing by Reference (Borrow)
Section titled “Passing by Reference (Borrow)”To allow a function to use a struct without taking ownership, pass a reference to it (&). This is almost always preferred for read-only access.
struct Article { title: String, author: String }
// This function borrows the Article immutably.fn display_article(article: &Article) { println!("Displaying article: '{}' by {}", article.title, article.author);}
fn main() { let my_article = Article { title: String::from("Understanding Borrows"), author: String::from("Rust Coder") };
display_article(&my_article); // We pass a reference.
// This is fine! `my_article` was only borrowed, not moved. println!("The article is still available: {}", my_article.title);}Defining Behavior with Methods
Section titled “Defining Behavior with Methods”Methods are functions that are defined within the context of a struct (or an enum or trait). They are declared using an impl (implementation) block. The first parameter of a method is always self, which represents the instance of the struct the method is being called on.
#[derive(Debug)]struct Rectangle { width: u32, height: u32,}
// Implementation block for the Rectangle struct.impl Rectangle { // This is a method. `&self` is a shorthand for `self: &Self`. // It borrows the instance immutably. fn area(&self) -> u32 { self.width * self.height }
// Another method to check if a rectangle can hold another. fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height }}
fn main() { let rect1 = Rectangle { width: 30, height: 50 }; let rect2 = Rectangle { width: 10, height: 40 };
println!( "The area of the rectangle is {} square pixels.", rect1.area() // Call the method using dot notation. );
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2));}Output
Section titled “Output”The area of the rectangle is 1500 square pixels.Can rect1 hold rect2? trueAssociated Functions (Static Methods)
Section titled “Associated Functions (Static Methods)”Functions within an impl block that do not take self as a parameter are called associated functions. They are associated with the struct type itself, not a specific instance. They are often used as constructors to create new instances of the struct. The idiomatic name for a constructor is new.
struct Point { x: f64, y: f64 }
impl Point { // An associated function, often used as a constructor. // It's called using the `::` syntax. fn new(x: f64, y: f64) -> Point { Point { x, y } // Field init shorthand syntax }
// Another associated function. fn origin() -> Point { Point { x: 0.0, y: 0.0 } }
// A method to display the point. fn display(&self) { println!("Point(x: {}, y: {})", self.x, self.y); }}
fn main() { // Call the `new` associated function. let p1 = Point::new(10.5, 20.2); p1.display();
// Call the `origin` associated function. let origin = Point::origin(); origin.display();}Output
Section titled “Output”Point(x: 10.5, y: 20.2)Point(x: 0.0, y: 0.0)Other Struct Varieties
Section titled “Other Struct Varieties”Rust also offers two other struct variations for different use cases:
- Tuple Structs: Useful when you want to give a tuple a name and make it a distinct type, but the fields don’t need names.
struct Color(u8, u8, u8); - Unit-Like Structs: Structs with no fields at all. They are useful in generic programming or when you need to implement a trait on a type but don’t have any data to store.
struct AlwaysEqual;