Structs

Rust has three kinds of structures:

  1. Named-field
  2. Tuple-like
  3. Unit-like

Term: The values contained within a struct, regardless of struct type, are called components.

A named-field struct gives a name to each component. A tuple-like struct identifies them by the order in which they appear. Unit-like structs have no components at all.

Structs are private by default, visible only in the module where they're declared. The sames goes for their fields.

Named-Field Structs

Convention: All types, structs includes, should have names in PascalCase.

Term: A struct expression is an expression that constructs a struct type:


#![allow(unused)]
fn main() {
// struct expression (similar to a constructor)
let r = std::ops::Range {
    start: 0,
    end: 9,
};
println!("Range length: {}", r.len());
}

Operator: If in a struct expression, the named fields are followed by ..EXPR, then any fields not mentioned take their values from EXPR, which must be another value of the same struct type.


#![allow(unused)]
fn main() {
let range_1 = 0..100;
println!("First range length: {}", range_1.len());
let range_2 = std::ops::Range {
    start: 50,
    ..range_1
};
println!("Second range length: {}", range_2.len());
}

Tuple-Like Structs

Term: The values held by a tuple-like structs are called elements.

Implicit Behavior: When you define a tuple-like struct, you implicitly create a function that constructs it:

struct Bounds(usize, usize);

This implicitly created this function:

fn Bounds(el0: usize, el1: usize) -> Bounds { ... }

Tuple-like structs are good for newtypes.

Term: Structs with a single component that you define to get stricter type checking are called newtypes.

Unit-Like Structs

Unit-like struct occupies no memory, much like the unit-type (). They're generally helpful when defining traits.

Struct Layout

In memory, both named-field and tuple-like structs are the same thing.

Defining Methods with impl

Concept: Rather than appearing inside the struct definition, as in C++ or Java, Rust methods appear in a separate impl block.

An impl block is a collection of fn definitions, each of which becomes a method on the struct type named at the top of the block.


#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Point { x: i64, y: i64 }

impl Point {
    // Static method that creates a new point from a tuple
    fn of(x: i64, y: i64) -> Self {
        Point { x, y }
    }
    // Method that swaps the x and y values
    fn inverse(self: &Self) -> Self {
        Point { x: self.y, y: self.x }
    }
    // Method that mutably add one points to this one
    fn add(self: &mut Self, add: &Self) {
        self.x = self.x + add.x;
        self.y = self.y + add.y;
    }
}

let mut p1 = Point::of(1, 2);
println!("{:?}", p1);
let p2 = p1.inverse();
println!("{:?}", p2);
p1.add(&p2);
println!("{:?}", p1);
}

Term: Methods defined in impls are called associated functions, since they're associated with a specific type. The opposite of an associated function (one not associated with any type) is called a free function.

Shorthand: Inside impl blocks, Rust automatically creates a type alias of the type for which the impl block is associated called Self.

A Rust method must explicitly use self to refer to the value it was called on.

Implicit Behavior: When you call a method, you don't need to borrow a mutable reference yourself; the ordinary method call syntax takes care of that implicitly. For example, in the above code, we call p1.add(&p2). This is the same as if we had called (&mut p1).add(&p2).

Term: Methods in impl blocks that don't take self as an argument become functions associated with the struct type itself, rather that a specific value of the type. These methods are called static methods. In the above code, Point::of is a static method of struct type Point.

Convention: It's conventional in Rust for static constructor functions to be named new.

Although you can have many separate impl blocks for a single type, they must all be in the same crate that defines that type.

Generic Structs

Term: In generic struct definitions, the type names used in are called type parameters.

Pronunciation: You can read the line impl<T> Queue<T> as something like "for any type T, here are some methods available on Queue".

Operator: For static method calls whose generic type parameter cannot be inferred, you can use the turbofish ::<> operator to specify the type:


#![allow(unused)]
fn main() {
let mut q = Queue::<char>::new();
}

Structs with Lifetime Parameters

Just as structs can have generic type parameters, they can have lifetime parameters as well.

Pronunciation: You can read the line struct Extrema<'elt> as something like, "given any specific life 'elt, you can make an Extrama<'elt> that holds references with that lifetime.

Rust always infers lifetime parameters for calls.

Shorthand: Because it's so common for the return type to use the same lifetime as an argument, Rust lets us omit the lifetimes when there's one obvious candidate.

Interior Mutability

Interior mutability is the principle of making a bit of data mutable inside an otherwise immutable value.

The two most straightforward mechanisms for implementing interior mutability are Cell<T> and RefCell<T>.

A Cell<T> is a struct that contains a single private value of type T. The only special thing about a Cell is that you can get and set the field even if you don't have mut access to the Cell itself.

Warning: Cells, and any types that contain them, are not thread-safe.

Come back to this. Probably won't need it for a while.