This content originally appeared on DEV Community and was authored by Abhishek Kumar
Moving beyond basic borrowing to master Rust’s powerful ownership tools and design patterns.
Recap: Your Journey So Far
Previous Articles on the same series
https://dev.to/triggerak/rust-ownership-mastery-the-zero-cost-safety-revolution-4p11
https://dev.to/triggerak/rust-series-borrow-checker-as-design-partner-understanding-lifetimes-through-analogies-84b
https://dev.to/triggerak/rust-series-borrow-checker-bonus-chapter-non-lexical-lifetimes-15cg
https://dev.to/triggerak/rust-series-borrow-checker-part-2-as-design-partner-the-compilers-mental-model-3en8
https://dev.to/triggerak/rust-series-borrow-checker-part-3-as-design-partner-common-errors-and-battle-tested-2da0
Now – Advanced patterns that make the borrow checker your ally
The Smart Pointer Toolkit
fn main() {
println!("=== Smart Pointers: Beyond Basic References ===");
demonstrate_smart_pointers();
println!("\n=== Design Patterns: Working WITH the Borrow Checker ===");
demonstrate_design_patterns();
println!("\n=== Performance Considerations ===");
demonstrate_performance_patterns();
}
// SMART POINTERS: Tools for complex ownership scenarios
fn demonstrate_smart_pointers() {
use std::rc::Rc;
use std::cell::RefCell;
use std::sync::Arc;
println!("=== Box<T>: Heap Allocation ===");
// CONCEPT: Box moves data to heap and provides ownership
let expensive_data = Box::new(vec![1; 1000]); // Large data on heap
let data_ref = &*expensive_data; // Deref to get &Vec<i32>
println!("Heap data length: {}", data_ref.len());
println!("Heap data - expensive_data {:?}", expensive_data);
println!("Heap data - *expensive_data {:?}", *expensive_data);
println!("Heap data - data_ref {:?}", data_ref);
println!("Heap data - *data_ref {:?}", *data_ref);
println!("\n=== Rc<T>: Shared Ownership (Single Thread) ===");
// CONCEPT: Reference counting for multiple owners
let shared_config = Rc::new("Global Configuration".to_string());
let user1 = Rc::clone(&shared_config); // Increment ref count
let user2 = Rc::clone(&shared_config); // Increment ref count
println!("Config: {}", shared_config);
println!("User1 sees: {}", user1);
println!("User2 sees: {}", user2);
println!("Reference count: {}", Rc::strong_count(&shared_config));
println!("\n=== RefCell<T>: Interior Mutability ===");
// CONCEPT: Runtime borrow checking instead of compile-time
let mutable_in_immutable = RefCell::new(vec![1, 2, 3]);
{
let mut borrowed = mutable_in_immutable.borrow_mut();
borrowed.push(4);
println!("Modified: {:?}", *borrowed);
} // Borrow released here
let read_only = mutable_in_immutable.borrow();
println!("Read-only view: {:?}", *read_only);
println!("\n=== Rc<RefCell<T>>: Shared Mutable State ===");
// CONCEPT: Combine shared ownership with interior mutability
let shared_counter = Rc::new(RefCell::new(0));
let incrementer1 = Rc::clone(&shared_counter);
let incrementer2 = Rc::clone(&shared_counter);
// Simulate different parts of code modifying shared state
*incrementer1.borrow_mut() += 10;
*incrementer2.borrow_mut() += 5;
println!("Final counter value: {}", shared_counter.borrow());
}
// DESIGN PATTERNS: Architecting around ownership
fn demonstrate_design_patterns() {
println!("=== Pattern 1: Builder with Ownership Transfer ===");
struct ConfigBuilder {
database_url: Option<String>,
port: Option<u16>,
debug: bool,
}
struct Config {
database_url: String,
port: u16,
debug: bool,
}
impl ConfigBuilder {
fn new() -> Self {
Self {
database_url: None,
port: None,
debug: false,
}
}
// PATTERN: Consume self and return Self for chaining
fn database_url(mut self, url: String) -> Self {
self.database_url = Some(url);
self // Return owned self
}
fn port(mut self, port: u16) -> Self {
self.port = Some(port);
self
}
fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
// PATTERN: Final consumption to create the target type
fn build(self) -> Result<Config, &'static str> {
Ok(Config {
database_url: self.database_url.ok_or("Database URL required")?,
port: self.port.unwrap_or(5432),
debug: self.debug,
})
}
}
let config = ConfigBuilder::new()
.database_url("postgresql://localhost".to_string())
.port(5433)
.debug(true)
.build()
.unwrap();
println!("Built config: {}:{}", config.database_url, config.port);
println!("\n=== Pattern 2: Resource Manager with RAII ===");
struct FileManager {
filename: String,
is_open: bool,
}
impl FileManager {
fn open(filename: String) -> Self {
println!("Opening file: {}", filename);
Self { filename, is_open: true }
}
fn write(&mut self, data: &str) {
if self.is_open {
println!("Writing to {}: {}", self.filename, data);
}
}
}
impl Drop for FileManager {
fn drop(&mut self) {
if self.is_open {
println!("Auto-closing file: {}", self.filename);
}
}
}
{
let mut file = FileManager::open("data.txt".to_string());
file.write("Important data");
// File automatically closed when dropped
}
println!("\n=== Pattern 3: Visitor with Lifetime Bounds ===");
trait Processor {
fn process(&self, data: &str) -> String;
}
struct Logger;
impl Processor for Logger {
fn process(&self, data: &str) -> String {
format!("[LOG] {}", data)
}
}
struct Encryptor;
impl Processor for Encryptor {
fn process(&self, data: &str) -> String {
format!("[ENCRYPTED] {}", data.chars().rev().collect::<String>())
}
}
// PATTERN: Accept any processor without lifetime complications
fn process_data(data: &str, processor: &dyn Processor) -> String {
processor.process(data)
}
let logger = Logger;
let encryptor = Encryptor;
let result1 = process_data("sensitive data", &logger);
let result2 = process_data("sensitive data", &encryptor);
println!("Logged: {}", result1);
println!("Encrypted: {}", result2);
}
// PERFORMANCE: When to use each pattern
fn demonstrate_performance_patterns() {
println!("=== Performance Pattern 1: Avoid Unnecessary Clones ===");
// SLOW: Cloning large data
fn process_slow(data: Vec<String>) -> Vec<String> {
data.into_iter()
.map(|s| s.to_uppercase()) // Could avoid allocation here
.collect()
}
// FASTER: Work with references when possible
fn process_fast(data: &[String]) -> Vec<String> {
data.iter()
.map(|s| s.to_uppercase())
.collect()
}
let data = vec!["hello".to_string(), "world".to_string()];
let result = process_fast(&data); // No ownership transfer needed
println!("Processed: {:?}", result);
println!("Original still available: {:?}", data);
}
ADVANCED PATTERNS SUMMARY:
-
SMART POINTERS:
- Box: Single ownership, heap allocation
- Rc: Multiple ownership, single thread
- Arc: Multiple ownership, multi-thread
- RefCell: Interior mutability with runtime checks
-
DESIGN PATTERNS:
- Builder: Consume and return Self for fluent APIs
- RAII: Automatic resource cleanup with Drop
- Visitor: Accept trait objects for flexibility
-
PERFORMANCE PATTERNS:
- Reference over ownership when possible
- Cow for conditional allocation
About Me
https://www.linkedin.com/in/kumarabhishek21/
This content originally appeared on DEV Community and was authored by Abhishek Kumar