Struct

Defining and Instantiating Structs

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("someone@example.com"),
        sign_in_count: 1,
    };
}

Note that the entire instance must be mutable; Rust doesn’t allow us to mark only certain fields as mutable. As with any expression, we can construct a new instance of the struct as the last expression in the function body to implicitly return that new instance.

The email and sign_in_count is of type String. We want each instance of the struct owns all of its data. If we use a &str, the variables are not owned by the struct instance.

fn build_user(email: String, username: String) -> User {
  User {
    active: true
    username,
    email,
    sign_in_count: 1,
  }
}

It uses field init shorthand syntax.

fn main() {
    // --snip--

    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}

This is struct update syntax. The ..user1 must come last to specify that any remaining fields should get their values from the corresponding fields in user1, but we can choose to specify values for as many fields as we want in any order, regardless of the order of the fields in the struct’s definition.

Note that this is a move. We can no longer use user1 as a whole as a whole after creating user2.

In this example, we can no longer use user1 as a whole after creating user2 because the String in the username field of user1 was moved into user2. If we had given user2 new String values for both email and username, and thus only used the active and sign_in_count values from user1, then user1 would still be valid after creating user2. Both active and sign_in_count are types that implement the Copy trait, so the behavior we discussed in the “Stack-Only Data: Copy” section would apply.

Tuple Structs

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

An Example Program Using Structs

struct Rectangle {
  width: u32,
  height: u32,
}

fn main() {
  let rect1 = Rectangle {
    width: 30,
    height: 50,
  };
  
  println!(
  	"The area of the rectangle is {} square pixels.",
     area(&rect1)
  );
}

fn area(rectangle: &Rectangle) -> u32 {
  rectangle.width * rectangle.height
}

To print out the structure we can use an output format called Debug.

// this is an outer attribute
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let rect1 = Rectangle {
        width: 30,
        height: 50,
    };

    println!("rect1 is {:?}", rect1);
}

We can use {:#?} instead of {:?} in the println! string for larger structs.

Another way to print out a value using the Debug format is to use the dbg! macro, which takes the ownership of an expression (as opposed to println!, which takes a reference), prints the file and line number of where that dbg! macro call ocuurs in your code along with the resultant value of that expression, and returns ownership of the value.

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let scale = 2;
    let rect1 = Rectangle {
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
}

Method Syntax

To define the function within the context of Rectangle, we start an impl block for Rectangle

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

The &self is actually short for self: &Self. Within an impl block, the type Self is an alias for the type that the impl block is for.

If we wanted to change the instance that we’ve called the method on as part of what the method does, we’d use &mut self as the first parameter.

Methods with the same name as a field usually return the value in the field and do nothing else. Methods like this are called getters, and Rust does not implement them automatically for struct fields as some other languages do.

Methods with More Parameters

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

Associated Functions

We can define associated functions that don’t have self as their first parameter (and thus are not methods) because they don’t need an instance of the type to work with.

The Self keywords in the return type and in the body of the function are aliases for the type that appears after the impl keyword, which in this case is Rectangle.

To call this associated function, we use the :: syntax with the struct name; let sq = Rectangle::square(3); is an example. This function is namespaced by the struct: the :: syntax is used for both associated functions and namespaces created by modules.

impl Rectangle {
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
}

Last updated