Fundamentals

Macros

Macros in rust are "code that writes code". For simplicity, they can be thought of as:

  • a function call, like: println!()
  • an attribute to add metadata to some item, like: #[derive(Debug)]
  • a way to define new syntax, like: vec![]

Macros are powerful, for in-depth understanding and usage, see the Macros chapter. Here lists some commonly used macros.

// function-like macro
println!("Hello, world!");             // print to stdout
eprintln!("An error occurred: {}", e); // print to stderr
dbg!(Object);                          // debug print

// attribute-like macro
#[derive(Debug)] // derive `Debug` trait for the struct
struct Object;

Common Types

Rust has a powerful ans expressive type system. Here are some commonly used non-primitive types.

String       // growable UTF-8 encoded text
Vec<T>       // growable array
Option<T>    // optional value. Rust's version of `null` or something
Result<T, E> // failable value
Box<T>       // heap-allocated value
&str         // string slice
&[T]         // array slice

Ownership & Borrowing

  • Ownership
    • Every value in Rust has a variable that's its owner.
    • When the owner goes out of scope, the value gets dropped.
    • When copying a value, the ownership is transferred. (if type not Copy)
  • Borrowing
    • A reference to a value, without taking ownership.
    • At any given time, there can be either one mutable reference or any number of immutable references.
    • References must always be valid.

Error Handling

In Rust, there're two ways to handle errors: panic!() and Result<T, E>. panic!() terminates the program, indicating a unrecoverable error; while Result<T, E> is used to handle recoverable errors. For when to panic!(), see The Book.

enum AppError {
    SaplynIsNotCat,
}

// failable function, returns `Result<T, E>`
fn failable() -> Result<String, AppError> {
    if saplyn.is_cat() {
        Ok(String::from("I'm a cat!"))
    } else {
        Err(AppError::SaplynIsNotCat)
    }
}

fn main() {
    let result = failable(); // call failable function
    match result {
        // is value? compute value
        Ok(val) => println!("Got value: {}", val),
        // is error? handle error
        Err(err) => {
            // unrecoverable error, panic!
            panic!("What?! I should be a cat!")
        },
    }
}

Sometimes, we are not handling the error right away, but passing it to the caller. In this case, we use ? to propagate the error.

// https://lib.rs/crates/anyhow

fn compute() -> anyhow::Result<i32> { Ok(114) }

fn action() -> anyhow::Result<i32> {
    let val = compute()?;  // propagate error or extracting value
    Ok(val * 1000 + 514)
}

fn long_chain() -> anyhow::Result<()> {
    let a = action()?;
    let b = action()?;
    let c = action()?;
    Ok(a + b + c)
}