Complex types
So far, we have been using simple types. In our business, we're typically dealing with complex data types. In Rust,
we use struct to name and group together multiple values that somehow belong together.
We can adopt our previous example to handle a more complex data type.
struct Person { name: String, age: u8, } fn age_group(person: &Person) -> String { if person.age > 150 { panic!("age is out of range"); } if person.age < 10 { return "child".to_string(); } if person.age >= 18 { return "adult".to_string(); } format!("teenager of {} years old", person.age) } fn main() { let person = Person { name: "Marcel".to_string(), age: 40, }; let description = age_group(&person); println!("{} is a {}", person.name, description); }
In this example, we've grouped "name" and "age" in a structure called "Person". We've typed age to be of type u8,
and name to be a String. We're borrowing person to the age_group function, such that we can still use it
later on in our println! statement.
In Rust, struct content is not only limited to types, but you can actually add functionality to a struct type.
Let's say we want to add the age_group() function to Person, we can rewrite the example like this:
struct Person { name: String, age: u8, } impl Person { fn age_group(&self) -> String { if self.age > 150 { panic!("age is out of range"); } if self.age < 10 { return "child".to_string(); } if self.age >= 18 { return "adult".to_string(); } format!("teenager of {} years old", self.age) } } fn main() { let person = Person { name: "Marcel".to_string(), age: 40, }; println!("{} is a {}", person.name, person.age_group()); }
Notice that the
age_groupmethod is borrowing self (&self) in order to reference its own properties.
Functions that take
&selfas the first parameter, are referred to as methods
For smaller struct types, it may be fine to create the struct directly, like in the above example. Often however,
it is a better choice to implement a new function to construct the structure with the mandatory fields:
struct Person { name: String, age: u8, } impl Person { fn new(name: String, age: u8) -> Self { Person { name, age } } fn age_group(&self) -> String { if self.age > 150 { panic!("age is out of range"); } if self.age < 10 { return "child".to_string(); } if self.age >= 18 { return "adult".to_string(); } format!("teenager of {} years old", self.age) } } fn main() { let person = Person::new("Marcel".to_string(), 40); println!("{} is a {}", person.name, person.age_group()); }
Notice that the
newfunction is not referencing&self. Other languages may call thenewfunction a static function. In Rust these types of functions are called associated functions. The function is returningSelfwhich is the same as if it was returningPerson.
Also notice the shorthands for
ageandnamein thenew()function wherePersonis constructed.
Destructuring structs
You can destructure a struct into its parts with a simple let statement:
struct Person { name: String, age: u8, } impl Person { fn new(name: String, age: u8) -> Self { Person { name, age } } } fn main() { let person = Person::new("Marcel".to_string(), 40); let Person { name: my_name, age } = person; println!("{my_name} is {age} years old"); }