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:
- Define a struct for a crew member with a name, age, and any other attributes you would like.
- Implement a few methods on this struct, such as one to say who it is.