Resource Lifecycle
Layer 2: Design Choices
Core Question
When should this resource be created, used, and cleaned up?
Before implementing lifecycle:
-
What's the resource's scope?
-
Who owns the cleanup responsibility?
-
What happens on error?
Lifecycle Pattern → Implementation
Pattern When Implementation
RAII Auto cleanup Drop trait
Lazy init Deferred creation OnceLock , LazyLock
Pool Reuse expensive resources r2d2 , deadpool
Guard Scoped access MutexGuard pattern
Scope Transaction boundary Custom struct + Drop
Thinking Prompt
Before designing lifecycle:
What's the resource cost?
-
Cheap → create per use
-
Expensive → pool or cache
-
Global → lazy singleton
What's the scope?
-
Function-local → stack allocation
-
Request-scoped → passed or extracted
-
Application-wide → static or Arc
What about errors?
-
Cleanup must happen → Drop
-
Cleanup is optional → explicit close
-
Cleanup can fail → Result from close
Trace Up ↑
To domain constraints (Layer 3):
"How should I manage database connections?" ↑ Ask: What's the connection cost? ↑ Check: domain-* (latency requirements) ↑ Check: Infrastructure (connection limits)
Question Trace To Ask
Connection pooling domain-* What's acceptable latency?
Resource limits domain-* What are infra constraints?
Transaction scope domain-* What must be atomic?
Trace Down ↓
To implementation (Layer 1):
"Need automatic cleanup" ↓ m02-resource: Implement Drop ↓ m01-ownership: Clear owner for cleanup
"Need lazy initialization" ↓ m03-mutability: OnceLock for thread-safe ↓ m07-concurrency: LazyLock for sync
"Need connection pool" ↓ m07-concurrency: Thread-safe pool ↓ m02-resource: Arc for sharing
Quick Reference
Pattern Type Use Case
RAII Drop trait Auto cleanup on scope exit
Lazy Init OnceLock , LazyLock
Deferred initialization
Pool r2d2 , deadpool
Connection reuse
Guard MutexGuard
Scoped lock release
Scope Custom struct Transaction boundaries
Lifecycle Events
Event Rust Mechanism
Creation new() , Default
Lazy Init OnceLock::get_or_init
Usage &self , &mut self
Cleanup Drop::drop()
Pattern Templates
RAII Guard
struct FileGuard { path: PathBuf, _handle: File, }
impl Drop for FileGuard { fn drop(&mut self) { // Cleanup: remove temp file let _ = std::fs::remove_file(&self.path); } }
Lazy Singleton
use std::sync::OnceLock;
static CONFIG: OnceLock<Config> = OnceLock::new();
fn get_config() -> &'static Config { CONFIG.get_or_init(|| { Config::load().expect("config required") }) }
Common Errors
Error Cause Fix
Resource leak Forgot Drop Implement Drop or RAII wrapper
Double free Manual memory Let Rust handle
Use after drop Dangling reference Check lifetimes
E0509 move out of Drop Moving owned field Option::take()
Pool exhaustion Not returned Ensure Drop returns
Anti-Patterns
Anti-Pattern Why Bad Better
Manual cleanup Easy to forget RAII/Drop
lazy_static!
External dep std::sync::OnceLock
Global mutable state Thread unsafety OnceLock or proper sync
Forget to close Resource leak Drop impl
Related Skills
When See
Smart pointers m02-resource
Thread-safe init m07-concurrency
Domain scopes m09-domain
Error in cleanup m06-error-handling