Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Smart Pointers

Store different types in one vector with Box

std Rust patterns

Defines the trait Notification that requires the implementation of the function send(&self). Implement Notification trait for Email and Sms. Creates a vector that holds different types that implement the same trait Notification and Box<T>. Iterate over that vector calling the method send(&self).

Trait objects such as dyn Notification do not have a size known at compile time, so they cannot be stored directly by value. Box<T> stores the value behind a pointer with a known size, making it possible to use different Notification implementations.

trait Notification {
    fn send(&self);
}

struct Email {
    address: String,
}

struct Sms {
    phone_number: String,
}

impl Notification for Email {
    fn send(&self) {
        println!("Sending email notification to {}", self.address);
    }
}

impl Notification for Sms {
    fn send(&self) {
        println!("Sending sms notification to {}", self.phone_number);
    }
}

fn main() {
    let email = Email {
        address: "example@mail.com".to_string(),
    };
    let sms = Sms {
        phone_number: "1-800-555-0100".to_string(),
    };

    let notifications: Vec<Box<dyn Notification>> = vec![Box::new(email), Box::new(sms)];

    for notification in notifications {
        notification.send();
    }
}

Share mutable structure between owners with Rc and RefCell

std Rust patterns

Uses Rc<T> to share ownership of one task between multiple dependents and RefCell<T> to mutate that shared task through RefCell::try_borrow_mut. This recipe updates one shared task and reads the result through each dependent using RefCell::borrow.

This pattern is useful when several parts of a single-threaded program need access to the same owned value, but mutations have borrows checked through RefCell.

use std::cell::{BorrowMutError, RefCell};
use std::rc::Rc;

#[derive(Debug)]
struct Task {
    name: String,
    done: bool,
    dependencies: Vec<Rc<RefCell<Task>>>,
}

fn new_task(name: &str) -> Rc<RefCell<Task>> {
    Rc::new(RefCell::new(Task {
        name: name.to_owned(),
        done: false,
        dependencies: Vec::new(),
    }))
}

fn main() -> Result<(), BorrowMutError> {
    let generate_api_contract = new_task("Generate API contract");
    let implement_endpoint_1 = new_task("Implement endpoint 1 from API contract");
    let implement_endpoint_2 = new_task("Implement endpoint 2 from API contract");

    {
        let mut borrowed_task_1 = implement_endpoint_1.try_borrow_mut()?;
        borrowed_task_1
            .dependencies
            .push(Rc::clone(&generate_api_contract));

        let mut borrowed_task_2 = implement_endpoint_2.try_borrow_mut()?;
        borrowed_task_2
            .dependencies
            .push(Rc::clone(&generate_api_contract));

        generate_api_contract.borrow_mut().done = true;
    }

    assert!(implement_endpoint_1.borrow().dependencies[0].borrow().done);
    assert!(implement_endpoint_2.borrow().dependencies[0].borrow().done);

    println!("{:?}", implement_endpoint_1.borrow().dependencies);
    println!("{:?}", implement_endpoint_2.borrow().dependencies);
    Ok(())
}

Return borrowed or owned text with Cow

std Rust patterns

Uses borrow::Cow to avoid allocating when the input string already has the desired form. The function returns Cow::Borrowed for unchanged input and Cow::Owned only when normalization requires a new String, such as converting uppercase text with str::to_lowercase. This keeps the fast path allocation-free while still allowing transformed output.

use std::borrow::Cow;

fn normalize(input: &str) -> Cow<'_, str> {
    if input.chars().any(|c| c.is_uppercase()) {
        Cow::Owned(input.to_lowercase())
    } else {
        Cow::Borrowed(input)
    }
}

fn main() {
    let owned = normalize("Changes are REQUIRED, this will be Cow::Owned");
    let borrowed = normalize("no changes required, this will be cow::borrowed");

    assert!(matches!(owned, Cow::Owned(_)));
    assert!(matches!(borrowed, Cow::Borrowed(_)));

    println!("{}", owned);
    println!("{}", borrowed);
}

Update text using swap, take, and replace

std Rust patterns

Uses mem::swap, mem::take, and mem::replace to move owned String values between document fields without cloning. mem::swap exchanges the drafts of two documents, mem::take moves a draft out for publishing while leaving an empty String behind, and mem::replace installs the new published text while returning the previous version.

use std::mem::{replace, swap, take};

struct Document {
    name: &'static str,
    draft: String,
    published: String,
}

impl Document {
    fn publish(&mut self) -> String {
        let next_version = take(&mut self.draft);
        replace(&mut self.published, next_version)
    }
}

fn main() {
    let mut guide = Document {
        name: "Guide",
        draft: "Guide Version 2".to_string(),
        published: "Guide Version 1".to_string(),
    };

    let mut release_notes = Document {
        name: "Release notes",
        draft: "Release notes for version 2".to_string(),
        published: "Release notes for version 1".to_string(),
    };

    swap(&mut guide.draft, &mut release_notes.draft);

    println!("Swapped:");
    println!("{} draft: {}", guide.name, guide.draft);
    println!("{} draft: {}", release_notes.name, release_notes.draft);

    let previous_version = guide.publish();

    assert!(guide.draft.is_empty());
    assert_eq!(guide.published, "Release notes for version 2");
    assert_eq!(previous_version, "Guide Version 1");
}