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-badge cat-rust-patterns-badge

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-badge cat-rust-patterns-badge

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-badge cat-rust-patterns-badge

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-badge cat-rust-patterns-badge

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");
}