Rust

Attributes

  • Metadata applied to some module, crate, or item;

  • Written as #[attribute];

  • Used to:

    • Conditional compilation of code;

    • Set crate name, version, author, and type (binary or library);

    • Disable lints (warnings);

    • Enable compiler features (macros, glob imports, etc.);

    • Link to a foreign library;

    • Mark functions as unit tests;

    • Mark functions that will be part of a benchmark;

    • Mark the entry point of the program;

    • ...

Some examples:

// `#[derive(Debug)]` automatically implements the `Debug` trait
#[derive(Debug)]
struct Foo {
    a: i32,
    b: i32,
}

// `#[cfg(test)]` marks the following module as a test module
#[cfg(test)
mod tests {
    // `#[test]` marks the following function as a unit test
    #[test]
    fn test_foo() {
        // ...
    }
}

Derive

  • Derive is an attribute;

  • Automatically implements some traits for a type, saving us from writing boilerplate code.

#[derive(PartialEq)]
struct Foo<T> {
    a: i32,
    b: T,
}

This is equivalent to:

impl<T: PartialEq> PartialEq for Foo<T> {
    fn eq(&self, other: &Foo<T>) -> bool {
        self.a == other.a && self.b == other.b
    }
}

dyn

  • dyn is a keyword used to specify a trait object;

enum vs struct

  • Enums are types that always result in one of a few variants (like an OR);

  • Structs are types that each instance can have different properties (like an AND);

  • Enums are useful for match statements, structs are useful for storing data.

enum MessageStatus {
    Read,
    Unread,
    Deleted,
}

struct Message {
    status: MessageStatus,
    content: String,
}

fn main() {
    // `message` = `Message` struct instance containing multiple properties
    let message = Message {
        // `status` is either `Read` or `Unread`
        status: MessageStatus::Unread,
        content: String::from("Hello, world!"),
    };
    
    // Every variant of an enum should be evaluated in a match statement
    match message.status {
        MessageStatus::Read => println!("Message read"),
        MessageStatus::Unread => println!("Message unread"),
        MessageStatus::Deleted => println!("Message deleted"),
    }
    
    // A catch-all match statement can be used to ignore some variants
    match message.status {
        MessageStatus::Read => println!("Message read"),
        // Ignore `Unread` & `Deleted` variants
        _ => println!("Unknown behavior"),
    }
}

Generics

  • Define any data type, or ones implementing one or more traits;

  • Specified inside angle brackets (<>);

  • Can be used in structs, enums, functions, methods, and impl blocks.

// Any type
struct Point<T> {
    x: T,
    y: T,
}

// `impl<T>` is required to use `T` in the following block
impl<T> Point<T> {
    fn new(x: T, y: T) -> Self {
        Point { x, y }
    }
}

// Only types implementing the `Ord` trait
fn sort<T: Ord>(list: &mut [T]) {
    // ...
}

// Multiple generic types
enum ComputationResult<T, E> {
    Success(T),
    Failure(E),
}

Lifetime

  • Lifetime is the scope for which a variable is valid;

  • A single quote (') is used to specify the lifetime of a variable;

  • Can be generic (declared like a generic type);

  • If not explicitly declared, the compiler infers the lifetime of a variable (implicit).

// `plate` cannot live longer than `car` (explicit)
struct Car<'a> {
    plate: &'a str,
    max_speed: u32,
}

fn main() {
    let car = Car { plate: "ABC123", max_speed: 200 };
    println!("{}", car.plate);
    
    // `message` is valid for the entire scope of the `main` function (implicit)
    let message = String::from("Hello, world!");
    println!("{}", message);

    // `name` is valid for the entire program (explicit)
    let name: &'static str = "John";
    println!("{}", name);
}

String vs str

  • String is a mutable, growable heap-allocated string, implemented by a struct wrapping a Vec<u8>;

  • str is an immutable, fixed length string slice.

trait

  • Collection of methods;

  • Contains functions declarations & default implementations;

  • Can be implemented by any type.

trait Animal<'a> {
    // Method declarations
    fn new(name: &'a str) -> Self;
    
    // Method definitions with default implementation
    fn move(&self, destination: &'a str) {
        println!("{} moves to {}", self.name, destination);
    }
}

struct Horse<'a> {
    name: &'a str,
}

impl<'a> Animal<'a> for Horse<'a> {
    // Define the methods declared in the trait
    fn new(name: &'a str) -> Horse<'a> {
        Horse { name: name }
    }
    
    // Override the default implementation
    fn move(&self, destination: &'a str) {
        println!("{} gallops to {}", self.name, destination);
    }
}

Last updated