Smart Pointers
Store different types in one vector with Box
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
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
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
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");
}