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()); }