Structs
We need to keep track of three pieces of information for each ticket:
- A title
- A description
- A status
We can start by using a String
to represent them. String
is the type defined in Rust's standard library to represent
UTF-8 encoded text.
But how do we combine these three pieces of information into a single entity?
Defining a struct
A struct
defines a new Rust type.
#![allow(unused)] fn main() { struct Ticket { title: String, description: String, status: String } }
A struct is quite similar to what you would call a class or an object in other programming languages.
Defining fields
The new type is built by combining other types as fields.
Each field must have a name and a type, separated by a colon, :
. If there are multiple fields, they are separated by a comma, ,
.
Fields don't have to be of the same type, as you can see in the Configuration
struct below:
#![allow(unused)] fn main() { struct Configuration { version: u32, active: bool } }
Instantiation
You can create an instance of a struct by specifying the values for each field:
#![allow(unused)] fn main() { // Syntax: <StructName> { <field_name>: <value>, ... } let ticket = Ticket { title: "Build a ticket system".into(), description: "A Kanban board".into(), status: "Open".into() }; }
Accessing fields
You can access the fields of a struct using the .
operator:
#![allow(unused)] fn main() { // Field access let x = ticket.description; }
Methods
We can attach behaviour to our structs by defining methods.
Using the Ticket
struct as an example:
#![allow(unused)] fn main() { impl Ticket { fn is_open(self) -> bool { self.status == "Open" } } // Syntax: // impl <StructName> { // fn <method_name>(<parameters>) -> <return_type> { // // Method body // } // } }
Methods are pretty similar to functions, with two key differences:
- methods must be defined inside an
impl
block - methods may use
self
as their first parameter.self
is a keyword and represents the instance of the struct the method is being called on.
self
If a method takes self
as its first parameter, it can be called using the method call syntax:
#![allow(unused)] fn main() { // Method call syntax: <instance>.<method_name>(<parameters>) let is_open = ticket.is_open(); }
This is the same calling syntax you used to perform saturating arithmetic operations on u32
values
in the previous chapter.
Static methods
If a method doesn't take self
as its first parameter, it's a static method.
#![allow(unused)] fn main() { struct Configuration { version: u32, active: bool } impl Configuration { // `default` is a static method on `Configuration` fn default() -> Configuration { Configuration { version: 0, active: false } } } }
The only way to call a static method is by using the function call syntax:
#![allow(unused)] fn main() { // Function call syntax: <StructName>::<method_name>(<parameters>) let default_config = Configuration::default(); }
Equivalence
You can use the function call syntax even for methods that take self
as their first parameter:
#![allow(unused)] fn main() { // Function call syntax: // <StructName>::<method_name>(<instance>, <parameters>) let is_open = Ticket::is_open(ticket); }
The function call syntax makes it quite clear that ticket
is being used as self
, the first parameter of the method,
but it's definitely more verbose. Prefer the method call syntax when possible.
Exercise
The exercise for this section is located in 03_ticket_v1/01_struct