Structs

  • A struct or structure lets you create a group of related fields
  • A struct is like the set of fields in a class (i.e., in Object-Oriented Programming)
  • A struct is similar to tuples, but with structs the fields are given names and structs can have functions associated with them

Declaring a struct

  • You declare a struct with the struct keyword. For example, consider the Employee struct below:
struct Employee {
    id: i32, 
    first_name: String,
    middle_name: String,
    last_name: String,
}
fn main() {}

Instantiating a struct

You instantiate a struct as follows:

struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}
fn main() {
    let emp = Employee {
        id : 11,
        first_name : String::from("Olu"),
        middle_name : String::from("Timi"),
        last_name : String::from("Toyin")
    };
} 

Accessing a struct's fields

struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}
fn main() {
    let emp = Employee {
        id : 11,
        first_name : String::from("Olu"),
        middle_name : String::from("Timi"),
        last_name : String::from("Toyin")
    };
    
    println!("{}:{} {} {}", emp.id, emp.first_name, emp.middle_name, emp.last_name);
} 

Changing the value assigned to a struct's field

  • To update the value assigned to a struct's field, you must make it mutable using the mut keyword:
struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}

fn main() {
    let mut emp = Employee {
        id : 11,
        first_name : String::from("Olu"),
        middle_name : String::from("Timi"),
        last_name : String::from("Toyin")
    };
    
    println!("{}:{} {} {}", emp.id, emp.first_name, emp.middle_name, emp.last_name);
    emp.middle_name = String::from("Tayo");
    println!("{}:{} {} {}", emp.id, emp.first_name, emp.middle_name, emp.last_name);    
} 

Printing a struct

If you ordinarily pass an instance of your struct to println!, Rust will complain that your struct doesn't implementstd::fmt::Display and it suggests to you to use {:?} (or {:#?} for pretty-print) instead in the placeholder for println. Run the below snippet to see.

struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}

fn main() {
    let emp = Employee {
        id : 11,
        first_name : String::from("Olu"),
        middle_name : String::from("Timi"),
        last_name : String::from("Toyin")
    };
    
    println!("{}", emp);
}

Let's update the println!("{}", emp); as follows:

struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}

fn main() {
    let emp = Employee {
        id : 11,
        first_name : String::from("Olu"),
        middle_name : String::from("Timi"),
        last_name : String::from("Toyin")
    };
    
    println!("{:?}", emp);
    println!("{:#?}", emp);
}

Now it complains that your struct doesn't implement Debug. We can fix this by adding the attribute: #[derive(Debug)] to the top of your struct definition like so:

#[derive(Debug)]
struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}

fn main() {
    let emp = Employee {
        id : 11,
        first_name : String::from("Olu"),
        middle_name : String::from("Timi"),
        last_name : String::from("Toyin")
    };
    
    println!("{:?}", emp);
    println!("{:#?}", emp);
}

Returning a struct from a function

You can return a struct from a function:

#[derive(Debug)]
struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}

fn main() {
  let emp = an_employee(String::from("Olu"), String::from("Time"), String::from("Jacobs"));
  println!("{:?}", emp);
}

fn an_employee(first_name: String, middle_name: String, last_name: String) -> Employee {
    Employee {
        id : 12,
        first_name : first_name,
        middle_name : middle_name,
        last_name : last_name
    }
}

Use the Field init shorthand syntax

In the previous snippet, the parameters of function an_employee has the same name as the struct's fields. In such a case, we can shorten the field_name: parameter_name to simply parameter_name like so:

#[derive(Debug)]
struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}

fn main() {
  let emp = an_employee(String::from("Olu"), String::from("Time"), String::from("Jacobs"));
  println!("{:?}", emp);
}

fn an_employee(first_name: String, middle_name: String, last_name: String) -> Employee {
    Employee {
        id : 12,
        first_name,
        middle_name,
        last_name
    }
}

Updating a struct using the struct update syntax

The struct update syntax allows you to create a new struct from an existing struct's fields and only change the values you want to change. In the snippet below, we create emp1 and use it to create emp2 only changing the value of middle_name from emp1.

#[derive(Debug)]
struct Employee {
   id: i32, 
   first_name: String,
   middle_name: String,
   last_name: String,
}

fn main() {
  let emp1 = an_employee(String::from("Olu"), String::from("Timi"), String::from("Jacobs"));
  println!("{:?}", emp1);
  
  let emp2 = Employee {
        middle_name : String::from("John"),
        ..emp1
  };
  println!("{:?}", emp2);
}

fn an_employee(first_name: String, middle_name: String, last_name: String) -> Employee {
    Employee {
        id : 12,
        first_name : first_name,
        middle_name : middle_name,
        last_name : last_name
    }
}

Tuple structs

They are structs which don't have names associated with their fields; rather they just have the types. Tuple structs are useful when you want to give the whole tuple a name and make the tuple a different type from other tuples (see tuple structs ) For example,

  #[derive(Debug)]
  struct Point(i32, i32);
  
  fn main() {
    let point1 = Point(3, 10);
    
    println!("{:?}", point1);
  }

Destructure tuple structs

You can also destructure tuple structs in a similar fashion to the way you do with tuples but in the case of tuple structs you must also specify the name of the struct. See example below showing tuple structs and an ordinary tuple.

  #[derive(Debug)]
  struct Point(i32, i32);
  
  fn main() {
    let point1 = Point(3, 10); //tuple struct
    let point2 = (4, 15); // ordinary tuple
 
    println!("Point 1: {:?}", point1);
    println!("Point 2: {:?}", point2);
    
    let Point(a, b) = point1; //destructuring mentions the name of the struct
    let (x, y) = point2; // destructuring just uses the normal tuple syntax i.e., (x, y)
    
    println!("{}, {}",a, b);
    println!("{}, {}",x, y);
  }

Unit structs

A unit struct is a struct which has no data in it. It is simply defined as follows:

#![allow(unused)]
fn main() {
struct Foo;
}

Unit structs can be useful when you want to implement a trait on some type but don’t have any data that you want to store in the type itself. (source:unit structs )

Ownership of Struct data

All the examples we have seen so far have used types owned by the struct such as String instead of &str and types which implement the Copy trait. t’s also possible for structs to store references to data owned by something else, but to do so requires the use of lifetimes (source: ownership of struct data). Lifetimes ensure that the data referenced by a struct is valid for as long as the struct is. We'll discuss this more later.

Associated functions in structs

It is possible to create functions within the context of a struct. Thus, such functions are called associate functions because they are defined within the struct. To define an associated function you do so in that's called an impl definition for the struct. For example, consider the following Time struct and an associated function called new for instantiating an instance of the struct

#[derive(Debug)]
struct Time {
   hour: u8,
   minute: u8,
   second: u8
}

impl Time {
   fn new(hour: u8, minute: u8, second: u8) -> Self {
      Self {
         hour, //showcasing field init syntax
         minute: minute,
         second: second
      }
   }
}

fn main() {
   let play_time = Time::new(12, 30, 0);
   println!("{:?} is play time.", play_time);
}

In the above, note that in the function body we are instantiating a Self and that the return type of the new function is also a Self. Self is a special keyword which is just an alias for the struct in which this associated function resides in, i.e., Time struct in this case. Thus, we could have just specified Time everywhere we have used Self.

Methods in structs

mod data {

    pub struct AgeHeight { 
        age: i32, 
        height: f64
    }


    impl AgeHeight {
        pub fn new(age:i32, height: f64) -> Self {
            AgeHeight { age: age, height:height }
        }

        pub fn get_age(&self) -> i32 {
            self.age
        }

        pub fn get_height(&self) -> f64 {
            self.height
        }
    }

}

fn main() {

    let age_height1 = data::AgeHeight::new(42, 1.85);
    let age_height2 = data::AgeHeight::new(41, 1.70);

    println!("Age {} height {}", age_height1.get_age(), age_height1.get_height());
    println!("Age {} height {}", age_height2.get_age(), age_height2.get_height());
 }

Passing a struct to a function

When the struct contains only types that implement Copy trait

When the struct contains only types that implement Drop trait

When the struct contains types that implement Copy as well as those that implement Drop