kota's memex

Like in many other languages structs in rust are a primitive for grouping together related fields of data.

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

When creating an instance of a struct it can be either shared or mutable. Rust doesn't allow marking individual 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. This is a common pattern for constructors style functions in rust.

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

methods

A method is defined within the context of a struct, an enum, or a trait object. Their first parameter is always self which represents the instance the method is being called on. Methods can take ownership of self, borrow self immutably, or borrow self mutably.

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

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

Rust supports automatic referencing and dereferencing so that when calling a method rust will automatically add in &, &mut, or * so object matches the signature of the method.

associated functions

All methods are considered associated functions because they are within the impl block for a struct. You can create other associated functions which are not methods. The most common of which is the "constructor pattern" which by convention are often named new. The from function of String::from is an associated function.

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

constructor naming

For a constructor that cannot fail the name new is usually appropriate. For constructors which instead return a Result, the name build is better.

multiple impl blocks

A struct can have multiple impl blocks. This is sometimes useful with traits and generics.

field init shorthand

When your function parameters and field names are exactly the same (such as in a constructor) rust allows a syntax that avoids repeating field names:

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

struct update syntax

When creating a new instance of a struct that is very similar to another instance you can use this syntax to only write out the differences. HOWEVER, this syntax will move any fields which do not implement the Copy trait. This means the original struct user1 cannot be used in full anymore:

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

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

tuple struct

Rust supports structs which look similar to tuples, but have the added meaning the struct name provides. Tuple structs are useful when you want to give the whole tuple a name and make the tuple a different type from other tuples, and when naming each field as in a regular struct would be verbose or redundant.

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

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

field-less structs

You can also define structs without any fields! These are called unit-like structs because they behave similarly to the unit type (empty tuple). Unit-like structs can be useful when you need to implement a trait on some type but don’t have any data that you want to store in the type itself.

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

lifetime annotations in struct fields

We can define structs to hold references, but in that case we would need to add a lifetime annotation on every reference in the struct's definition:

struct ImportantExcept<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentense = novel.split('.').next().unwrap();
    let i = ImportantExcept {
        part: first_sentense,
    };
}

This type annotation means instances of ImportantExcept cannot outlive the reference it holds in its part field.