Structs in Rust
About This Module
This module introduces Rust's struct (structure) types, which allow you to create custom data types by grouping related values together with named fields. Structs provide more semantic meaning than tuples by giving names to data fields and are fundamental for building complex data models.
Prework
Prework Readings
Read the following sections from "The Rust Programming Language" book:
- Chapter 5: Structs Introduction
- Chapter 5.1: Defining and Instantiating Structs
- Chapter 5.2: An Example Program Using Structs
Pre-lecture Reflections
Before class, consider these questions:
- How do structs provide more semantic meaning than tuples?
- What are the advantages of named fields over positional access?
- How do tuple structs combine benefits of both tuples and structs?
- When would you choose structs over other data structures?
- How do structs help with type safety and preventing logical errors?
Learning Objectives
By the end of this module, you should be able to:
- Define and instantiate regular structs with named fields
- Create and use tuple structs for type safety
- Access and modify struct fields
- Use struct update syntax for efficient instantiation
- Understand when to use structs vs. tuples vs. other data types
- Apply structs in enum variants for complex data modeling
- Design data structures using struct composition
What Are Structs?
Definition:
A struct (structure) is a custom data type that lets you name and package together multiple related values. Unlike tuples, structs give meaningful names to each piece of data.
Key Benefits:
- Semantic meaning: Named fields make code self-documenting
- Type safety: Prevent mixing up different types of data
- Organization: Group related data logically
- Maintainability: Changes are easier when fields have names
Structs
Previously we saw tuples, e.g., (12, 1.7, true), where we can mix different types of data.
Structs compared to tuples:
- Similar: can hold items of different types
- Different: the items have names
#![allow(unused)] fn main() { // Definition: list items (called fields) // and their types struct Person { name: String, year_born: u16, time_100m: f64, likes_ice_cream: bool, } }
Struct Instantiation
- Replace types with values
struct Person { name: String, year_born: u16, time_100m: f64, likes_ice_cream: bool, } fn main() { let mut cartoon_character: Person = Person { name: String::from("Tasmanian Devil"), year_born: 1954, time_100m: 7.52, likes_ice_cream: true, }; }
Struct Field Access
- Use "." to access fields
struct Person { name: String, year_born: u16, time_100m: f64, likes_ice_cream: bool, } fn main() { let mut cartoon_character: Person = Person { name: String::from("Tasmanian Devil"), year_born: 1954, time_100m: 7.52, likes_ice_cream: true, }; // Accessing fields: use ".field_name" println!("{} was born in {}", cartoon_character.name, cartoon_character.year_born); cartoon_character.year_born = 2022; println!("{} was born in {}", cartoon_character.name, cartoon_character.year_born); }
Challenge: How would we update the last println! statement to print
Tasmanian Devil was born in 2022, can run a mile in 7.52 seconds and likes ice cream ?
Tuple Structs
Example: The tuple (f64,f64,f64) could represent:
- box size (e.g., height width depth)
- Euclidean coordinates of a point in 3D
We can use tuple structs to give a name to a tuple and make it more meaningful.
fn main() { struct BoxSize(f64,f64,f64); struct Point3D(f64,f64,f64); let mut my_box = BoxSize(3.2,6.0,2.0); let mut p : Point3D = Point3D(-1.3,2.1,0.0); }
Tuple Structs, cont.
- Impossible to accidentally confuse different types of triples.
- No runtime penalty! Verified at compilation.
fn main() { struct BoxSize(f64,f64,f64); struct Point3D(f64,f64,f64); let mut my_box = BoxSize(3.2,6.0,2.0); let mut p : Point3D = Point3D(-1.3,2.1,0.0); // won't work my_box = p; }
Tuple Structs, cont.
- Acessing via index
- Destructuring
fn main() { struct Point3D(f64,f64,f64); let mut p : Point3D = Point3D(-1.3,2.1,0.0); // Acessing via index println!("{} {} {}",p.0,p.1,p.2); p.0 = 17.2; // Destructuring let Point3D(first,second,third) = p; println!("{} {} {}", first, second, third); }
Named structs in enums
Structs with braces and exchangable with tuples in many places
enum LPSolution { None, Point{x:f64,y:f64} } fn main() { let example = LPSolution::Point{x:1.2, y:4.2}; if let LPSolution::Point{x:first,y:second} = example { println!("coordinates: {} {}", first, second); }; }
How is that different from enum variants with values?
enum LPSolution2 { None, Point(f64,f64) } fn main() { let example = LPSolution2::Point(1.2, 4.2); if let LPSolution2::Point(first,second) = example { println!("coordinates: {} {}", first, second); }; }
Recap and Next Steps
Recap
- Structs are a way to group related data together
- Tuple structs are a way to give a name to a tuple
- Named structs in enums are a way to group related data together
- Structs are critical to Rust's OO capabilities
Next Steps
- We will see how connect structs to methods (e.g. functions)
- Important step towards Object-Oriented style of programming in Rust