Structs

  • Good explanatory video .

  • Unlike with tuples, in a struct you’ll name each piece of data so it’s clear what the values mean. Adding these names means that structs are more flexible than tuples: you don’t have to rely on the order of the data to specify or access the values of an instance.

Creation

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
  • 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);
    }
    

Creating methods

  • The struct methods' first parameter is always self , which represents the instance of the struct the method is being called on.

    • The &self  is actually short for self: &Self .

      • The type Self  is an alias for the type that the impl  block is for.

    • Methods can take ownership of self , borrow self  immutably, as we’ve done here, or borrow self  mutably, just as they can any other parameter.

    • We chose &self  here, as we don’t want to take ownership, and we just want to read the data in the struct, not write to it.

  • All functions defined within an impl  block are called associated functions  because they’re associated with the type named after the impl .

    • Everything within this impl  block will be associated with the Rectangle  type.

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

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

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

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}
  • Using multiple impl

    • There’s no reason to separate these methods into multiple impl  blocks here, but this is valid syntax:

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

Creating an instance

  • Directly :

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

    fn build_user(email: String, username: String) -> User {
        User {
            active: true,
            username: username,  // can be written just 'username'
            email: email,        // can be written just 'email'
            sign_in_count: 1,
        }
    }
    
  • Constructors :

    • Associated functions that aren’t methods are often used for constructors that will return a new instance of the struct.

      • These are often called new , but new  isn’t a special name and isn’t built into the language.

      • For example, we could choose to provide an associated function named square  that would have one dimension parameter and use that as both width and height, thus making it easier to create a square Rectangle  rather than having to specify the same value twice

    impl Rectangle {
        fn square(size: u32) -> Self {
            Self {
                width: size,
                height: size,
            }
        }
    }
    
    • 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 .

      let sq = Rectangle::square(3);
      

Access

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

    user1.email = String::from("anotheremail@example.com");
}
  • Note that the entire instance must be mutable; Rust doesn’t allow us to mark only certain fields as mutable.

  • No need for -> access operator :

    • Rust automatically adds in & , &mut , or *  so object  matches the signature of the method.

    (&p1).distance(&p2);
    
    // Same thing
    p1.distance(&p2);