Structs

Structuring your data is a key part of programming, and every language worth its salt gives you a way to do that. In object-oriented languages, this is usually by creating a class. In Rust, you structure your data with structs.

A struct is a named grouping of fields (data belonging to the struct), which also can have methods on it. Let's unpack that.

When you're creating a struct, first you have to give it a name:

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

Then you can also put fields on it, of any type:

#![allow(unused)] fn main() { struct PirateShip { captain: String, crew: Vec<String>, treasure: f64, } }

And you can have methods on the struct by using an impl block. Those methods can take a reference to self (&self) if they are just reading fields, or they can use a mutable reference (&mut self) if they will be changing any data.

#![allow(unused)] fn main() { struct PirateShip { captain: String, crew: Vec<String>, treasure: f64, } impl PirateShip { pub fn count_treasure(&self) -> f64 { // some computations probably self.treasure } pub fn mutiny(&mut self) { if self.crew.len() > 0 { // replace the captain with one of the crew self.captain = self.crew.pop().unwrap(); } else { println!("there's no crew to perform mutiny"); } } } }

To create an instance of a struct, you give the name of the struct along with a value for each of the fields, specified by name (like treasure: 64.0). There is also some shorthand to use: if you have a variable in scope with the same name as one of the fields, you can specify that just by name. That's confusing without an example, so let's see it in action.

#![allow(unused)] fn main() { struct PirateShip { captain: String, crew: Vec<String>, treasure: f64, } let blackbeard = "Blackbeard".to_owned(); let crew = vec!["Scurvy".to_owned(), "Rat".to_owned(), "Polly".to_owned()]; let ship = PirateShip { captain: blackbeard, crew, treasure: 64.0, }; }

In this example, we can see both forms of specifying fields. The captain and treasure are specified with the <name>: <value> form, while the crew is specified with the shorthand that means crew: crew.

Note that in this example, we used the method to_owned a few times. This takes a reference to a string (&str) and creates an owned string (String), so that we don't have to worry about lifetimes. The precise details of this aren't particularly relevant in this chapter, but it's a nice thing to keep in mind: if you want to avoid including lifetimes, you can use owned instances by cloning (or a method like to_owned). There's more complexity with strings in Rust than in other languages due to references and lifetimes, but further treatment of them is beyond the scope of this course.

Exercises:

  1. Define a struct for a crew member with a name, age, and any other attributes you would like.
  2. Implement a few methods on this struct, such as one to say who it is.