Memory Management Patterns
Rust’s ownership system influences how we manage resources:
RAII (Resource Acquisition Is Initialization)
RAII is a fundamental pattern in Rust where resources are acquired during initialization and released when the object goes out of scope:
struct File {
handle: std::fs::File,
}
impl File {
fn new(path: &str) -> Result<Self, std::io::Error> {
let handle = std::fs::File::open(path)?;
Ok(File { handle })
}
fn read_to_string(&mut self) -> Result<String, std::io::Error> {
let mut content = String::new();
self.handle.read_to_string(&mut content)?;
Ok(content)
}
}
// The file is automatically closed when `file` goes out of scope
fn process_file(path: &str) -> Result<String, std::io::Error> {
let mut file = File::new(path)?;
file.read_to_string()
}
Drop Guard
A drop guard ensures that cleanup code runs even if a function returns early:
struct CleanupGuard<F: FnMut()> {
cleanup: F,
}
impl<F: FnMut()> Drop for CleanupGuard<F> {
fn drop(&mut self) {
(self.cleanup)();
}
}
fn with_cleanup<F: FnMut()>(cleanup: F) -> CleanupGuard<F> {
CleanupGuard { cleanup }
}
fn process_data() -> Result<(), std::io::Error> {
// Set up some resource
let resource = setup_resource()?;
// Create a guard that will clean up the resource
let _guard = with_cleanup(|| {
cleanup_resource(&resource);
});
// Process the resource (may return early)
if let Err(e) = process_resource(&resource) {
return Err(e);
}
// More processing (may also return early)
if let Err(e) = more_processing(&resource) {
return Err(e);
}
// The guard will clean up the resource when it goes out of scope
Ok(())
}
Scoped Operations
Scoped operations ensure that resources are properly managed within a specific scope:
use std::sync::{Mutex, MutexGuard};
struct Database {
data: Mutex<Vec<String>>,
}
impl Database {
fn new() -> Self {
Database {
data: Mutex::new(Vec::new()),
}
}
// Returns a scoped guard that provides access to the data
fn access(&self) -> Result<MutexGuard<Vec<String>>, std::sync::PoisonError<MutexGuard<Vec<String>>>> {
self.data.lock()
}
}
fn main() {
let db = Database::new();
// The scope ensures the lock is released when `data` goes out of scope
{
let mut data = db.access().unwrap();
data.push("Hello".to_string());
data.push("World".to_string());
// Lock is automatically released here
}
// We can acquire the lock again
{
let data = db.access().unwrap();
println!("Data: {:?}", *data);
}
}
Creational Patterns
Patterns for object creation in Rust:
Builder Pattern
The builder pattern allows for flexible object construction with a fluent interface:
#[derive(Debug, Default)]
struct HttpRequest {
method: String,
url: String,
headers: std::collections::HashMap<String, String>,
body: Option<Vec<u8>>,
}
#[derive(Debug)]
struct HttpRequestBuilder {
request: HttpRequest,
}
impl HttpRequestBuilder {
fn new() -> Self {
HttpRequestBuilder {
request: HttpRequest::default(),
}
}
fn method(mut self, method: impl Into<String>) -> Self {
self.request.method = method.into();
self
}
fn url(mut self, url: impl Into<String>) -> Self {
self.request.url = url.into();
self
}
fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.request.headers.insert(key.into(), value.into());
self
}
fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
self.request.body = Some(body.into());
self
}
fn build(self) -> HttpRequest {
self.request
}
}
fn main() {
let request = HttpRequestBuilder::new()
.method("GET")
.url("https://example.com")
.header("User-Agent", "Rust")
.header("Accept", "text/html")
.build();
println!("{:?}", request);
}