Rust Memory Management Best Practices¶
Objective: Master senior-level Rust memory management patterns for production systems. When you need to optimize memory usage, when you want to understand Rust's memory model, when you need enterprise-grade memory strategiesโthese best practices become your weapon of choice.
Core Principles¶
- Zero-Cost Abstractions: Memory management with no runtime overhead
- Ownership System: Leverage Rust's ownership for memory safety
- Smart Pointers: Use appropriate smart pointers for different scenarios
- Memory Layout: Optimize data structures for cache performance
- Resource Management: Proper cleanup and resource management
Memory Allocation Patterns¶
Smart Pointers¶
// rust/01-smart-pointers.rs
/*
Smart pointer patterns and best practices for Rust
*/
use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;
use std::sync::Mutex;
/// Demonstrates Rc (Reference Counted) patterns.
pub struct RcDemo {
data: Rc<String>,
count: usize,
}
impl RcDemo {
pub fn new(data: String) -> Self {
Self {
data: Rc::new(data),
count: 1,
}
}
pub fn clone_data(&self) -> Rc<String> {
Rc::clone(&self.data)
}
pub fn get_strong_count(&self) -> usize {
Rc::strong_count(&self.data)
}
pub fn get_data(&self) -> &str {
&self.data
}
}
/// Demonstrates Arc (Atomically Reference Counted) patterns.
pub struct ArcDemo {
data: Arc<String>,
count: usize,
}
impl ArcDemo {
pub fn new(data: String) -> Self {
Self {
data: Arc::new(data),
count: 1,
}
}
pub fn clone_data(&self) -> Arc<String> {
Arc::clone(&self.data)
}
pub fn get_strong_count(&self) -> usize {
Arc::strong_count(&self.data)
}
pub fn get_data(&self) -> &str {
&self.data
}
}
/// Demonstrates RefCell patterns for interior mutability.
pub struct RefCellDemo {
data: RefCell<String>,
count: usize,
}
impl RefCellDemo {
pub fn new(data: String) -> Self {
Self {
data: RefCell::new(data),
count: 1,
}
}
pub fn get_data(&self) -> String {
self.data.borrow().clone()
}
pub fn set_data(&self, new_data: String) {
*self.data.borrow_mut() = new_data;
}
pub fn update_data<F>(&self, f: F)
where
F: FnOnce(&mut String),
{
f(&mut self.data.borrow_mut());
}
}
/// Demonstrates Mutex patterns for thread-safe interior mutability.
pub struct MutexDemo {
data: Arc<Mutex<String>>,
count: usize,
}
impl MutexDemo {
pub fn new(data: String) -> Self {
Self {
data: Arc::new(Mutex::new(data)),
count: 1,
}
}
pub fn get_data(&self) -> String {
self.data.lock().unwrap().clone()
}
pub fn set_data(&self, new_data: String) {
*self.data.lock().unwrap() = new_data;
}
pub fn update_data<F>(&self, f: F)
where
F: FnOnce(&mut String),
{
f(&mut self.data.lock().unwrap());
}
}
/// Demonstrates Box patterns for heap allocation.
pub struct BoxDemo {
data: Box<String>,
count: usize,
}
impl BoxDemo {
pub fn new(data: String) -> Self {
Self {
data: Box::new(data),
count: 1,
}
}
pub fn get_data(&self) -> &str {
&self.data
}
pub fn get_data_mut(&mut self) -> &mut String {
&mut self.data
}
pub fn into_data(self) -> String {
*self.data
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rc_demo() {
let demo = RcDemo::new("Hello".to_string());
assert_eq!(demo.get_strong_count(), 1);
let cloned = demo.clone_data();
assert_eq!(demo.get_strong_count(), 2);
assert_eq!(cloned, "Hello");
}
#[test]
fn test_arc_demo() {
let demo = ArcDemo::new("Hello".to_string());
assert_eq!(demo.get_strong_count(), 1);
let cloned = demo.clone_data();
assert_eq!(demo.get_strong_count(), 2);
assert_eq!(cloned, "Hello");
}
#[test]
fn test_refcell_demo() {
let demo = RefCellDemo::new("Hello".to_string());
assert_eq!(demo.get_data(), "Hello");
demo.set_data("World".to_string());
assert_eq!(demo.get_data(), "World");
demo.update_data(|s| s.push_str("!"));
assert_eq!(demo.get_data(), "World!");
}
#[test]
fn test_mutex_demo() {
let demo = MutexDemo::new("Hello".to_string());
assert_eq!(demo.get_data(), "Hello");
demo.set_data("World".to_string());
assert_eq!(demo.get_data(), "World");
demo.update_data(|s| s.push_str("!"));
assert_eq!(demo.get_data(), "World!");
}
#[test]
fn test_box_demo() {
let mut demo = BoxDemo::new("Hello".to_string());
assert_eq!(demo.get_data(), "Hello");
*demo.get_data_mut() = "World".to_string();
assert_eq!(demo.get_data(), "World");
let data = demo.into_data();
assert_eq!(data, "World");
}
}
Memory Layout Optimization¶
// rust/02-memory-layout.rs
/*
Memory layout optimization patterns and best practices
*/
use std::mem;
/// Demonstrates SoA (Structure of Arrays) pattern.
pub struct SoAData {
x: Vec<f32>,
y: Vec<f32>,
z: Vec<f32>,
mass: Vec<f32>,
}
impl SoAData {
pub fn new(capacity: usize) -> Self {
Self {
x: Vec::with_capacity(capacity),
y: Vec::with_capacity(capacity),
z: Vec::with_capacity(capacity),
mass: Vec::with_capacity(capacity),
}
}
pub fn add_particle(&mut self, x: f32, y: f32, z: f32, mass: f32) {
self.x.push(x);
self.y.push(y);
self.z.push(z);
self.mass.push(mass);
}
pub fn compute_center_of_mass(&self) -> (f32, f32, f32) {
let mut total_mass = 0.0;
let mut weighted_x = 0.0;
let mut weighted_y = 0.0;
let mut weighted_z = 0.0;
for i in 0..self.x.len() {
let mass = self.mass[i];
total_mass += mass;
weighted_x += self.x[i] * mass;
weighted_y += self.y[i] * mass;
weighted_z += self.z[i] * mass;
}
if total_mass > 0.0 {
(
weighted_x / total_mass,
weighted_y / total_mass,
weighted_z / total_mass,
)
} else {
(0.0, 0.0, 0.0)
}
}
}
/// Demonstrates AoS (Array of Structures) pattern.
pub struct AoSData {
particles: Vec<Particle>,
}
#[derive(Clone, Copy)]
pub struct Particle {
pub x: f32,
pub y: f32,
pub z: f32,
pub mass: f32,
}
impl AoSData {
pub fn new(capacity: usize) -> Self {
Self {
particles: Vec::with_capacity(capacity),
}
}
pub fn add_particle(&mut self, particle: Particle) {
self.particles.push(particle);
}
pub fn compute_center_of_mass(&self) -> (f32, f32, f32) {
let mut total_mass = 0.0;
let mut weighted_x = 0.0;
let mut weighted_y = 0.0;
let mut weighted_z = 0.0;
for particle in &self.particles {
total_mass += particle.mass;
weighted_x += particle.x * particle.mass;
weighted_y += particle.y * particle.mass;
weighted_z += particle.z * particle.mass;
}
if total_mass > 0.0 {
(
weighted_x / total_mass,
weighted_y / total_mass,
weighted_z / total_mass,
)
} else {
(0.0, 0.0, 0.0)
}
}
}
/// Demonstrates memory-efficient data structures.
pub struct MemoryEfficientData {
data: Vec<u8>,
element_size: usize,
}
impl MemoryEfficientData {
pub fn new(element_size: usize, capacity: usize) -> Self {
Self {
data: vec![0; element_size * capacity],
element_size,
}
}
pub fn set_element(&mut self, index: usize, element: &[u8]) -> Result<(), &'static str> {
if element.len() != self.element_size {
return Err("Element size mismatch");
}
if index * self.element_size + self.element_size > self.data.len() {
return Err("Index out of bounds");
}
let start = index * self.element_size;
self.data[start..start + self.element_size].copy_from_slice(element);
Ok(())
}
pub fn get_element(&self, index: usize) -> Result<&[u8], &'static str> {
if index * self.element_size + self.element_size > self.data.len() {
return Err("Index out of bounds");
}
let start = index * self.element_size;
Ok(&self.data[start..start + self.element_size])
}
}
/// Demonstrates memory pool pattern.
pub struct MemoryPool<T> {
pool: Vec<T>,
available: Vec<usize>,
next_id: usize,
}
impl<T> MemoryPool<T> {
pub fn new(capacity: usize) -> Self {
Self {
pool: Vec::with_capacity(capacity),
available: Vec::new(),
next_id: 0,
}
}
pub fn allocate(&mut self, item: T) -> Result<usize, T> {
if let Some(index) = self.available.pop() {
self.pool[index] = item;
Ok(index)
} else if self.pool.len() < self.pool.capacity() {
let index = self.pool.len();
self.pool.push(item);
Ok(index)
} else {
Err(item)
}
}
pub fn deallocate(&mut self, index: usize) -> Result<T, &'static str> {
if index >= self.pool.len() {
return Err("Index out of bounds");
}
let item = std::mem::replace(&mut self.pool[index], unsafe { std::mem::uninitialized() });
self.available.push(index);
Ok(item)
}
pub fn get(&self, index: usize) -> Option<&T> {
self.pool.get(index)
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
self.pool.get_mut(index)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_soa_data() {
let mut soa = SoAData::new(100);
soa.add_particle(1.0, 2.0, 3.0, 1.0);
soa.add_particle(4.0, 5.0, 6.0, 2.0);
let (x, y, z) = soa.compute_center_of_mass();
assert!((x - 3.0).abs() < 1e-6);
assert!((y - 4.0).abs() < 1e-6);
assert!((z - 5.0).abs() < 1e-6);
}
#[test]
fn test_aos_data() {
let mut aos = AoSData::new(100);
aos.add_particle(Particle { x: 1.0, y: 2.0, z: 3.0, mass: 1.0 });
aos.add_particle(Particle { x: 4.0, y: 5.0, z: 6.0, mass: 2.0 });
let (x, y, z) = aos.compute_center_of_mass();
assert!((x - 3.0).abs() < 1e-6);
assert!((y - 4.0).abs() < 1e-6);
assert!((z - 5.0).abs() < 1e-6);
}
#[test]
fn test_memory_efficient_data() {
let mut data = MemoryEfficientData::new(4, 10);
let element = [1, 2, 3, 4];
data.set_element(0, &element).unwrap();
let retrieved = data.get_element(0).unwrap();
assert_eq!(retrieved, &element);
}
#[test]
fn test_memory_pool() {
let mut pool = MemoryPool::new(10);
let index1 = pool.allocate("Hello".to_string()).unwrap();
let index2 = pool.allocate("World".to_string()).unwrap();
assert_eq!(pool.get(index1), Some(&"Hello".to_string()));
assert_eq!(pool.get(index2), Some(&"World".to_string()));
let item = pool.deallocate(index1).unwrap();
assert_eq!(item, "Hello");
}
}
Garbage Collection Patterns¶
// rust/03-gc-patterns.rs
/*
Garbage collection patterns and best practices
*/
use std::rc::{Rc, Weak};
use std::cell::RefCell;
use std::collections::HashMap;
/// Demonstrates weak reference patterns.
pub struct WeakRefDemo {
data: Rc<String>,
weak_ref: Weak<String>,
}
impl WeakRefDemo {
pub fn new(data: String) -> Self {
let rc = Rc::new(data);
let weak = Rc::downgrade(&rc);
Self {
data: rc,
weak_ref: weak,
}
}
pub fn get_strong_count(&self) -> usize {
Rc::strong_count(&self.data)
}
pub fn get_weak_count(&self) -> usize {
Rc::weak_count(&self.data)
}
pub fn try_get_weak(&self) -> Option<Rc<String>> {
self.weak_ref.upgrade()
}
}
/// Demonstrates circular reference patterns.
pub struct Node {
pub data: String,
pub parent: Option<Weak<RefCell<Node>>>,
pub children: Vec<Rc<RefCell<Node>>>,
}
impl Node {
pub fn new(data: String) -> Self {
Self {
data,
parent: None,
children: Vec::new(),
}
}
pub fn add_child(&mut self, child: Rc<RefCell<Node>>) {
child.borrow_mut().parent = Some(Rc::downgrade(&Rc::new(RefCell::new(self.clone()))));
self.children.push(child);
}
pub fn get_parent(&self) -> Option<Rc<RefCell<Node>>> {
self.parent.as_ref().and_then(|p| p.upgrade())
}
}
impl Clone for Node {
fn clone(&self) -> Self {
Self {
data: self.data.clone(),
parent: self.parent.clone(),
children: self.children.clone(),
}
}
}
/// Demonstrates reference counting with cleanup.
pub struct RefCountedResource {
data: String,
cleanup_callback: Option<Box<dyn Fn()>>,
}
impl RefCountedResource {
pub fn new(data: String, cleanup_callback: Box<dyn Fn()>) -> Self {
Self {
data,
cleanup_callback: Some(cleanup_callback),
}
}
pub fn get_data(&self) -> &str {
&self.data
}
}
impl Drop for RefCountedResource {
fn drop(&mut self) {
if let Some(callback) = self.cleanup_callback.take() {
callback();
}
}
}
/// Demonstrates memory-efficient string interning.
pub struct StringInterner {
strings: Vec<String>,
indices: HashMap<String, usize>,
}
impl StringInterner {
pub fn new() -> Self {
Self {
strings: Vec::new(),
indices: HashMap::new(),
}
}
pub fn intern(&mut self, s: &str) -> usize {
if let Some(&index) = self.indices.get(s) {
index
} else {
let index = self.strings.len();
self.strings.push(s.to_string());
self.indices.insert(s.to_string(), index);
index
}
}
pub fn get(&self, index: usize) -> Option<&str> {
self.strings.get(index).map(|s| s.as_str())
}
pub fn len(&self) -> usize {
self.strings.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_weak_ref_demo() {
let demo = WeakRefDemo::new("Hello".to_string());
assert_eq!(demo.get_strong_count(), 1);
assert_eq!(demo.get_weak_count(), 1);
let strong = demo.try_get_weak().unwrap();
assert_eq!(*strong, "Hello");
}
#[test]
fn test_string_interner() {
let mut interner = StringInterner::new();
let id1 = interner.intern("Hello");
let id2 = interner.intern("World");
let id3 = interner.intern("Hello"); // Should return same ID
assert_eq!(id1, id3);
assert_ne!(id1, id2);
assert_eq!(interner.get(id1), Some("Hello"));
assert_eq!(interner.get(id2), Some("World"));
}
}
TL;DR Runbook¶
Quick Start¶
// 1. Smart pointers
use std::rc::Rc;
use std::sync::Arc;
use std::cell::RefCell;
let rc = Rc::new("Hello".to_string());
let arc = Arc::new("World".to_string());
let refcell = RefCell::new(42);
// 2. Memory layout optimization
struct SoAData {
x: Vec<f32>,
y: Vec<f32>,
z: Vec<f32>,
}
// 3. Memory pools
struct MemoryPool<T> {
pool: Vec<T>,
available: Vec<usize>,
}
// 4. Weak references
use std::rc::{Rc, Weak};
let rc = Rc::new("Hello".to_string());
let weak = Rc::downgrade(&rc);
Essential Patterns¶
// Complete memory management setup
pub fn setup_rust_memory_management() {
// 1. Smart pointers
// 2. Memory layout optimization
// 3. Garbage collection patterns
// 4. Memory pools
// 5. String interning
// 6. Weak references
// 7. Circular references
// 8. Resource cleanup
println!("Rust memory management setup complete!");
}
This guide provides the complete machinery for Rust memory management. Each pattern includes implementation examples, memory strategies, and real-world usage patterns for enterprise memory optimization.