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:

  1. methods must be defined inside an impl block
  2. 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