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_group
method is borrowing self (&self
) in order to reference its own properties.
Functions that take
&self
as 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
new
function is not referencing&self
. Other languages may call thenew
function a static function. In Rust these types of functions are called associated functions. The function is returningSelf
which is the same as if it was returningPerson
.
Also notice the shorthands for
age
andname
in thenew()
function wherePerson
is 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"); }