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:

Pre-lecture Reflections

Before class, consider these questions:

  1. How do structs provide more semantic meaning than tuples?
  2. What are the advantages of named fields over positional access?
  3. How do tuple structs combine benefits of both tuples and structs?
  4. When would you choose structs over other data structures?
  5. 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