Rust - Structs


Structs are essentially tuples with named fields. For example:

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

fn main() {
    // Normal Declaration
    let user1 = User {
        email: String::from("contact@daltoncole.com"),
        username: String::from("drc"),
        active: true,
        sign_in_count: 1,
    };

    let user2 = build_user_annoying_way(String::from("bob@bob.bob"), String::from("bob"));
    let user3 = build_user_easy_way(String::from("john@bob.com"), String::from("john"));

    // Struct Update (quick way to change only some fields from another instance)
    let user4 = User {
        email: String::from("me@daltoncole.com"),
        username: String::from("crd"),
        ..user1 // Copies the rest of the data from user 1
    };

    println!(
        "The users are: {}, {}, {}, and {}", 
        user1.username, user2.username, user3.username, user4.username
    )
}

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

// Since the variable names share the same name as the field,
//   we can just use the variables instead
fn build_user_easy_way(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

Methods

Struct methods are implemented in impl blocks. Multiple methods can be in a single impl block and you can have multiple impl blocks per struct.

The general syntax of methods are very similar to python methods, where instance methods require &self (just using self is allowed but rare). To call "class" level methods, the namespace operator :: is required.

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

impl Rectangle {
    // Self's type is automatically the struct's type
    fn area(&self) -> u32 {
        self.width * self.height
    }

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

impl Rectangle {
    // Example where you wouldn't use self
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

fn main() {
    let rect1 = Rectangle {
        width: 25,
        height: 40,
    };

    let rect2 = Rectangle {
        width: 100,
        height: 45,
    };

    println!("Area of rect1: {}", rect1.area());
    println!("Fits inside: {}", rect1.fits_inside(&rect2));

    // Create a square rectangle. Use the namespace syntax here
    let square = Rectangle::square(7);
    println!("Area of square: {}", square.area());
}

Tuple Structs

You can also create structs that behave like tuples, i.e. structs without named fields. This can be useful when you want to pass tuples that contain specific information into a function.

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

fn main() {
    let white = Color(255, 255, 255);
    let origin = Point(0, 0, 0);

    print_color(white);
    //print_color(origin); // Breaks
}

fn print_color(color: Color) {
    // Use "." followed by the index to index into a Tuple Struct
    println!("({}, {}, {})", color.0, color.1, color.2);
}