Skip to content

Rust Reference: Ownership, Borrowing, Result/Option, Cargo, Traits & Async/Tokio

Rust guarantees memory safety without a garbage collector through its ownership system. The three concepts — ownership, borrowing, and lifetimes — eliminate entire categories of bugs at compile time. Rust is used for system software, WebAssembly, embedded, and increasingly as a replacement for C/C++ in security-critical paths.

1. Ownership & Borrowing

The rules that make Rust memory-safe
// Ownership rules:
// 1. Each value has exactly one owner
// 2. When the owner goes out of scope, the value is dropped (freed)
// 3. You can MOVE or BORROW a value — not both at once

// Move (transfer ownership):
let s1 = String::from("hello");
let s2 = s1;          // s1 is MOVED to s2 — s1 is now invalid
// println!("{}", s1); // ERROR: s1 was moved

// Clone (deep copy — expensive, explicit):
let s1 = String::from("hello");
let s2 = s1.clone();  // s1 still valid — full copy
println!("{} {}", s1, s2);

// Copy types (integers, bools, chars — always copied, no move):
let x = 5;
let y = x;   // x is COPIED — both valid
println!("{} {}", x, y);

// Borrowing — references without taking ownership:
fn print_length(s: &String) {    // & = immutable borrow
    println!("Length: {}", s.len());
}
let s = String::from("hello");
print_length(&s);                // borrow s — s still valid after call

// Mutable borrow:
fn append(s: &mut String) {
    s.push_str(" world");
}
let mut s = String::from("hello");
append(&mut s);

// Borrow rules (enforced at compile time):
// - You can have MANY immutable borrows OR ONE mutable borrow — not both
// - References must always be valid (no dangling pointers)
let mut s = String::from("hello");
let r1 = &s;       // ok
let r2 = &s;       // ok — multiple immutable refs
// let r3 = &mut s; // ERROR: can't have mutable ref while immutable refs exist
println!("{} {}", r1, r2);
let r3 = &mut s;   // ok — r1, r2 no longer used after previous println
r3.push_str("!");

2. Error Handling with Result & Option

Result, Option, ?, and custom error types
// No null in Rust. Instead: Option
let maybe_name: Option = Some("Alice".to_string());
let nothing: Option = None;

// Pattern matching on Option:
match maybe_name {
    Some(name) => println!("Hello, {}", name),
    None => println!("No name"),
}

// Shorthand methods:
let name = maybe_name.unwrap_or("Anonymous".to_string());
let len = maybe_name.as_ref().map(|n| n.len()).unwrap_or(0);

// Result for operations that can fail:
use std::fs;
use std::io;

fn read_config() -> Result {
    fs::read_to_string("config.toml")   // returns Result
}

// The ? operator — propagate errors up (like throw in other languages):
fn parse_and_log() -> Result<(), Box> {
    let content = fs::read_to_string("config.toml")?;   // ? = return Err if error
    let value: serde_json::Value = serde_json::from_str(&content)?;
    println!("{}", value);
    Ok(())
}

// Custom error type with thiserror (recommended library):
use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
    #[error("IO error: {0}")]
    Io(#[from] io::Error),
    #[error("Config key {key} not found")]
    MissingKey { key: String },
    #[error("Parse failed: {0}")]
    Parse(#[from] serde_json::Error),
}
// #[from] auto-implements From for AppError

3. Cargo — Build, Test & Dependencies

Cargo.toml, workspace, common commands, and useful crates
# Cargo.toml:
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"              # Rust edition (2021 is current — use it)

[dependencies]
serde = { version = "1.0", features = ["derive"] }   # feature flags
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] }
anyhow = "1.0"                # flexible error handling
thiserror = "1.0"             # custom error types

[dev-dependencies]
mockall = "0.11"              # only used in tests

# Build profiles:
[profile.release]
opt-level = 3                 # max optimization
lto = true                    # link-time optimization (smaller + faster binary)
strip = true                  # strip debug symbols (much smaller Docker image)

# Common Cargo commands:
cargo new my-project          # create new binary project
cargo new --lib my-lib        # create library crate
cargo build                   # debug build (fast compile, slow binary)
cargo build --release         # optimized build
cargo run                     # build + run
cargo run -- arg1 arg2        # run with args
cargo test                    # run all tests
cargo test test_name          # run specific test
cargo test -- --nocapture     # show println! output in tests
cargo check                   # type-check without building (fast CI check)
cargo clippy                  # lint (catch common mistakes)
cargo fmt                     # format code
cargo doc --open              # generate + open docs
cargo update                  # update all deps to latest compatible versions
cargo tree                    # show dependency tree
cargo add serde --features derive  # add dep to Cargo.toml

4. Structs, Enums & Pattern Matching

Data types, impl blocks, traits, and exhaustive matching
// Struct with methods:
#[derive(Debug, Clone)]         // auto-implement Debug and Clone traits
struct User {
    id: u64,
    name: String,
    active: bool,
}

impl User {
    // Associated function (like static method — no self):
    fn new(id: u64, name: String) -> Self {
        Self { id, name, active: true }
    }

    // Method (takes &self = immutable borrow):
    fn display_name(&self) -> &str {
        &self.name
    }

    // Mutable method:
    fn deactivate(&mut self) {
        self.active = false;
    }
}

// Enums (can hold data — more powerful than C enums):
#[derive(Debug)]
enum Command {
    Quit,
    Move { x: i32, y: i32 },        // struct variant
    Write(String),                    // tuple variant
    ChangeColor(u8, u8, u8),
}

// Exhaustive pattern matching:
let cmd = Command::Move { x: 10, y: 20 };
match cmd {
    Command::Quit => println!("Quit"),
    Command::Move { x, y } => println!("Move to {},{}", x, y),
    Command::Write(msg) => println!("Write: {}", msg),
    Command::ChangeColor(r, g, b) => println!("Color: {} {} {}", r, g, b),
}

// if let (match single variant):
if let Command::Write(msg) = cmd {
    println!("Writing: {}", msg);
}

// Traits (like interfaces):
trait Summary {
    fn summarize(&self) -> String;
    fn preview(&self) -> String { format!("{}...", &self.summarize()[..50]) }  // default impl
}
impl Summary for User {
    fn summarize(&self) -> String { format!("User #{}: {}", self.id, self.name) }
}

5. Async/Await with Tokio

Async functions, spawning tasks, channels, and tokio::select!
// Tokio is the standard async runtime for Rust:
// Cargo.toml: tokio = { version = "1", features = ["full"] }

use tokio;
use tokio::time::{sleep, Duration};

#[tokio::main]                          // macro wraps main in async runtime
async fn main() -> Result<(), Box> {
    let result = fetch_user(1).await?;   // .await suspends current task, not thread
    println!("{:?}", result);
    Ok(())
}

async fn fetch_user(id: u64) -> Result {
    let response = reqwest::get(format!("https://api.example.com/users/{}", id))
        .await?
        .json::()
        .await?;
    Ok(response)
}

// Spawn concurrent tasks:
let task1 = tokio::spawn(async { fetch_user(1).await });
let task2 = tokio::spawn(async { fetch_user(2).await });
let (r1, r2) = tokio::join!(task1, task2);    // wait for both

// Channel (send data between tasks):
let (tx, mut rx) = tokio::sync::mpsc::channel::(32);   // buffer size 32
tokio::spawn(async move {
    tx.send("hello".to_string()).await.unwrap();
});
while let Some(msg) = rx.recv().await {
    println!("Received: {}", msg);
}

// tokio::select! (wait for first of multiple futures):
tokio::select! {
    result = fetch_user(1) => println!("User fetched: {:?}", result),
    _ = sleep(Duration::from_secs(5)) => println!("Timeout!"),
}

Track Rust and Cargo releases.
ReleaseRun monitors Rust, Go, Python, and 13+ technologies.

Related: Go Modules Reference | GitHub Actions Reference | Docker Reference | Rust EOL Tracker

🦀 Check your Rust deps: Use the Cargo.toml Health Checker to find outdated crates, yanked versions, and stale dependencies.

🔍 Free tool: Cargo.toml Batch Health Checker — paste your Cargo.toml and get health grades for all Rust crates at once.

Founded

2023 in London, UK

Contact

hello@releaserun.com