Drop Trait
Drop Trait
Level 3 — Ownership & Borrowing Custom destructor logic; called automatically when a value goes out of scope.
1. Prerequisites
- Ownership — The system that determines exactly when the Drop trait is triggered.
- Trait — (Future reference) The overarching system used to define shared interfaces and behaviors like
Drop.
2. Term Category
Rust-specific (the automation mechanism): C++ has "Destructors" and Python has __del__, but Rust's Drop trait is perfectly integrated with Ownership to ensure cleanup happens at the exact right millisecond, 100% of the time, without the need for a Garbage Collector.
3. Explanation
(1) Design Motivation — "Why did we design this?"
We learned in the Ownership chapter that when a variable goes out of scope, it is "dropped". But what does that actually mean?
If the variable is a String, "dropping" means freeing the memory on the Heap. But what if the variable represents an open File, a Network Socket, or a Database Connection? Those resources don't just need their memory freed; they need to be explicitly "closed" so the Operating System can reuse them.
Rust solves this with the Drop trait. It allows you to write a custom block of "cleanup code". The compiler guarantees that this cleanup code will execute the exact moment the variable goes out of scope, whether the function finishes normally or crashes early. You never have to write file.close() manually again!
(2) Reality Metaphor
Imagine renting a hotel room.
When your rental period is over (you go out of scope), you leave the room. The Drop trait is the specific checklist of chores that must happen the moment you leave.
For a simple integer, the checklist is completely empty. You just walk out. But for a Database Connection, the checklist involves logging out of the server, closing the network port, and returning the physical room key to the front desk.
Rust ensures this checklist is executed by an invisible robot the very millisecond you step out of the room.
(3) Rust Code Examples
Short Snippet (Writing Custom Cleanup)
To write custom cleanup code, you implement the Drop trait for your struct.
struct CustomSmartPointer {
data: String,
}
// We define the cleanup code here!
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer { data: String::from("my stuff") };
println!("CustomSmartPointer created.");
} // Scope ends here. `drop` is automatically called!
Output:
CustomSmartPointer created.
Dropping CustomSmartPointer with data `my stuff`!
Fuller Example (Early Drop and LIFO Order)
Variables are dropped in the reverse order of their creation (Last In, First Out). If you ever need to clean something up before the scope naturally ends, you can force an early drop using std::mem::drop.
fn main() {
let a = CustomSmartPointer { data: String::from("A") };
let b = CustomSmartPointer { data: String::from("B") };
println!("Variables created.");
// We cannot call `b.drop()` directly. The compiler prevents it.
// Instead, we pass ownership to the `drop` function to force early cleanup!
drop(b);
println!("End of main.");
} // `a` naturally drops here.
Output:
Variables created.
Dropping CustomSmartPointer with data `B`!
End of main.
Dropping CustomSmartPointer with data `A`!
4. Common Mistakes & Pitfalls
Mistake 1: Misunderstanding Drop Trait Scoping and Lifecycle Rules
The mistake: Assuming Drop Trait instances remain valid beyond their declaring scope block or across asynchronous boundaries without explicit lifetime tracking.
Why it's wrong: Rust strictly enforces lexical scope boundaries and non-lexical lifetimes (NLL) at compile time. Accessing dropped values or failing to handle variable drop order results in compiler errors such as E0597 or E0382.
Incorrect:
fn get_ref() -> &str {
let s = String::from("drop_trait_data");
&s // ❌ Error E0106/E0515: returns a reference to data owned by the current function
}
Fix:
fn get_string() -> String {
let s = String::from("drop_trait_data");
s // Ownership of the String is transferred directly to the caller
}
Mistake 2: Mutating Drop Trait State Without Exclusive Ownership or mut Borrowing
The mistake: Attempting to mutate data associated with Drop Trait through an immutable reference &T or without specifying mut in variable declarations.
Why it's wrong: Rust's aliasing XOR mutability rule (&T for shared immutable access, &mut T for exclusive mutable access) prohibits mutating state through shared references unless interior mutability patterns (e.g. RefCell, Mutex) are explicitly used.
Incorrect:
fn update_val(data: &i32) {
// *data += 1; // ❌ Error E0594: cannot assign to `*data`, which is behind a `&` reference
}
Fix:
fn update_val(data: &mut i32) {
*data += 1; // Correct: exclusive mutable reference permits mutation
}
Mistake 3: Concurrent Access to Drop Trait Across Threads Without Send / Sync Guards
The mistake: Sharing non-thread-safe Drop Trait instances across OS threads via std::thread::spawn.
Why it's wrong: Types that do not implement Send or Sync marker traits cannot safely cross thread boundaries. The compiler prevents data races by raising compile errors E0277 (trait Send is not implemented).
Incorrect:
use std::rc::Rc;
use std::thread;
let rc = Rc::new(42);
// thread::spawn(move || { println!("{}", rc); }); // ❌ Error E0277: `Rc` cannot be sent between threads safely
Fix:
use std::sync::Arc;
use std::thread;
let arc = Arc::new(42);
thread::spawn(move || {
println!("{}", arc); // Correct: `Arc` implements `Send` and `Sync`
});
5. Practice Exercises
Exercise 1: Thread-Safe RAII Resource Pool Guard with Automatic Health Recycling
Scenario:
In high-throughput microservices, constructing database connections or network sockets involves expensive round-trips. High-performance software uses a connection pool where worker threads acquire a temporary handle guard (PooledConnection). When a worker finishes its unit of work, the handle goes out of scope and is dropped automatically.
Design a thread-safe connection pool system where PooledConnection implements Drop to:
- Automatically return healthy connections back to the idle pool queue when dropped.
- Detect connections marked as unhealthy (e.g. corrupted by network timeouts or I/O errors) and discard them without returning them to the idle queue.
- Update connection pool metrics (
idle_connections,active_count, andrecycled_count) safely under a mutex without leaking resources or causing deadlocks.
Requirements:
- Implement
ConnectionPool,PooledConnection, andConnection. PooledConnectionmust holdconn: Option<Connection>and a reference handle toConnectionPool.- In
Drop::drop, useOption::take()to extract the connection value. Ifis_healthyistrue, return it toidle_connectionsand incrementrecycled_count. Iffalse, decrementactive_countwithout recycling. - Write unit tests covering normal acquire-release lifecycle, unhealthy connection discarding, and pool exhaustion behavior.
Answer
Implementation
use std::sync::{Arc, Mutex};
#[derive(Debug, PartialEq, Eq)]
pub struct Connection {
pub id: u64,
pub is_healthy: bool,
}
impl Connection {
pub fn new(id: u64) -> Self {
Self {
id,
is_healthy: true,
}
}
}
pub struct InnerPool {
pub idle_connections: Vec<Connection>,
pub active_count: usize,
pub recycled_count: usize,
}
#[derive(Clone)]
pub struct ConnectionPool {
inner: Arc<Mutex<InnerPool>>,
}
impl ConnectionPool {
pub fn new(initial_connections: Vec<Connection>) -> Self {
Self {
inner: Arc::new(Mutex::new(InnerPool {
idle_connections: initial_connections,
active_count: 0,
recycled_count: 0,
})),
}
}
pub fn acquire(&self) -> Option<PooledConnection> {
let mut guard = self.inner.lock().unwrap();
if let Some(conn) = guard.idle_connections.pop() {
guard.active_count += 1;
Some(PooledConnection {
conn: Some(conn),
pool: self.clone(),
})
} else {
None
}
}
pub fn stats(&self) -> (usize, usize, usize) {
let guard = self.inner.lock().unwrap();
(guard.idle_connections.len(), guard.active_count, guard.recycled_count)
}
}
pub struct PooledConnection {
conn: Option<Connection>,
pool: ConnectionPool,
}
impl PooledConnection {
pub fn mark_unhealthy(&mut self) {
if let Some(ref mut c) = self.conn {
c.is_healthy = false;
}
}
pub fn id(&self) -> u64 {
self.conn.as_ref().map(|c| c.id).unwrap_or(0)
}
}
impl Drop for PooledConnection {
fn drop(&mut self) {
if let Some(conn) = self.conn.take() {
let mut guard = self.pool.inner.lock().unwrap();
guard.active_count -= 1;
if conn.is_healthy {
guard.idle_connections.push(conn);
guard.recycled_count += 1;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_normal_lifecycle_and_recycling() {
let pool = ConnectionPool::new(vec![Connection::new(1), Connection::new(2)]);
assert_eq!(pool.stats(), (2, 0, 0));
{
let conn1 = pool.acquire();
assert!(conn1.is_some());
let conn1 = conn1.unwrap();
assert_eq!(conn1.id(), 2);
assert_eq!(pool.stats(), (1, 1, 0));
{
let conn2 = pool.acquire().unwrap();
assert_eq!(conn2.id(), 1);
assert_eq!(pool.stats(), (0, 2, 0));
} // conn2 drops here -> recycled into idle queue
assert_eq!(pool.stats(), (1, 1, 1));
} // conn1 drops here -> recycled into idle queue
assert_eq!(pool.stats(), (2, 0, 2));
}
#[test]
fn test_unhealthy_connection_discard() {
let pool = ConnectionPool::new(vec![Connection::new(10)]);
{
let mut conn = pool.acquire().unwrap();
assert_eq!(conn.id(), 10);
conn.mark_unhealthy();
} // drops unhealthy connection -> discarded
let (idle, active, recycled) = pool.stats();
assert_eq!(idle, 0);
assert_eq!(active, 0);
assert_eq!(recycled, 0);
assert!(pool.acquire().is_none());
}
#[test]
fn test_pool_exhaustion_and_drop_order() {
let pool = ConnectionPool::new(vec![Connection::new(100)]);
let c1 = pool.acquire();
assert!(c1.is_some());
let c2 = pool.acquire();
assert!(c2.is_none());
assert_ne!(c1.as_ref().unwrap().id(), 0);
drop(c1);
assert!(matches!(pool.acquire(), Some(c) if c.id() == 100));
}
}
Technical Explanation
- RAII Resource Management & Drop Hook: The
PooledConnectionstruct acts as a Resource Acquisition Is Initialization (RAII) guard wrapping a rawConnection. When a worker function finishes processing (or exits early due to an error?or panic),PooledConnectiongoes out of lexical scope and itsDropimplementation executes automatically. Option::takeDestructing Pattern: Rust'sDrop::drop(&mut self)receives a mutable reference&mut self. You cannot move owned fields out of a&mut selfborrow directly. Wrappingconnin anOption<Connection>permitsself.conn.take(), which extracts ownership of theConnectionwhile leavingNoneinPooledConnection. This avoids invalid memory reads and prevents double-drops.- Thread Safety & Mutex Guard Lifecycle: Shared state in
ConnectionPoolis guarded byArc<Mutex<InnerPool>>. InsideDrop::drop, acquiringself.pool.inner.lock().unwrap()locks the pool briefly to decrementactive_countand return healthy connections toidle_connections. Because the lock guard is confined todrop, the lock duration is minimal and deterministic. - Edge Cases & Invariants:
- Panic Safety: Even if a panic occurs during worker execution, stack unwinding runs destructors for all in-scope variables, ensuring connections are never leaked or left stuck in the active state.
- Health Filtering: Marking a connection unhealthy before drop guarantees broken sockets are pruned from the pool, maintaining pool health integrity without requiring expensive background validation threads.
Exercise 2: Transactional Storage Engine with Drop-Based Automatic Rollback Safety
Scenario:
In transactional storage engines and embedded key-value databases, operations must maintain strict atomicity. If a transaction attempts several writes but fails or exits before completion, all speculative writes must be cleanly undone.
Implement a Transaction wrapper over a StorageEngine that uses the Drop trait to implement automatic rollback safety (Scope-Bound Rollback Pattern).
- As mutations occur via
tx.put(key, val), the transaction speculatively updates the engine while preserving pre-transaction snapshots of modified keys in anoriginal_snapshotsmap. - If
tx.commit()is called, the transaction marks itselfcommitted = trueand disarms the rollback mechanism. - If
Transactionis dropped withoutcommit()having been called (e.g. early function return,?error propagation, or panic),Drop::droptriggers an automatic rollback, reverting all speculatively changed keys to their original snapshot states and removing newly inserted keys.
Requirements:
- Implement
StorageEngineandTransaction<'a>. TransactionborrowsStorageEnginemutably (&'a mut StorageEngine).commit(mut self) -> Result<(), TransactionError>setscommitted = trueand returnsOk(()).- In
Drop::drop, if!self.committed, iterate throughoriginal_snapshotsand restore values inself.engine.data. - Include unit tests verifying successful commits, automatic drop rollbacks, and error matching.
Answer
Implementation
use std::collections::HashMap;
#[derive(Debug, PartialEq, Eq)]
pub enum TransactionError {
AlreadyCommitted,
StorageLockFailed,
}
pub struct StorageEngine {
pub data: HashMap<String, String>,
}
impl StorageEngine {
pub fn new() -> Self {
Self {
data: HashMap::new(),
}
}
pub fn get(&self, key: &str) -> Option<&String> {
self.data.get(key)
}
pub fn begin_transaction<'a>(&'a mut self, tx_id: u64) -> Transaction<'a> {
Transaction {
tx_id,
engine: self,
uncommitted_changes: HashMap::new(),
original_snapshots: HashMap::new(),
committed: false,
}
}
}
pub struct Transaction<'a> {
tx_id: u64,
engine: &'a mut StorageEngine,
uncommitted_changes: HashMap<String, String>,
original_snapshots: HashMap<String, Option<String>>,
committed: bool,
}
impl<'a> Transaction<'a> {
pub fn put(&mut self, key: impl Into<String>, value: impl Into<String>) {
let key = key.into();
let value = value.into();
if !self.original_snapshots.contains_key(&key) {
let original = self.engine.data.get(&key).cloned();
self.original_snapshots.insert(key.clone(), original);
}
self.uncommitted_changes.insert(key.clone(), value.clone());
self.engine.data.insert(key, value);
}
pub fn commit(mut self) -> Result<(), TransactionError> {
if self.committed {
return Err(TransactionError::AlreadyCommitted);
}
self.committed = true;
self.original_snapshots.clear();
self.uncommitted_changes.clear();
Ok(())
}
pub fn tx_id(&self) -> u64 {
self.tx_id
}
}
impl<'a> Drop for Transaction<'a> {
fn drop(&mut self) {
if !self.committed {
for (key, original_val) in self.original_snapshots.drain() {
match original_val {
Some(val) => {
self.engine.data.insert(key, val);
}
None => {
self.engine.data.remove(&key);
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_commit_persists_changes() {
let mut engine = StorageEngine::new();
engine.data.insert("k1".into(), "v1".into());
{
let mut tx = engine.begin_transaction(101);
tx.put("k1", "v1_updated");
tx.put("k2", "v2_new");
assert_eq!(tx.tx_id(), 101);
let res = tx.commit();
assert!(res.is_ok());
} // Drop runs, but committed flag is true -> no rollback
assert_eq!(engine.get("k1"), Some(&"v1_updated".to_string()));
assert_eq!(engine.get("k2"), Some(&"v2_new".to_string()));
}
#[test]
fn test_transaction_implicit_drop_triggers_rollback() {
let mut engine = StorageEngine::new();
engine.data.insert("k1".into(), "v1_initial".into());
{
let mut tx = engine.begin_transaction(202);
tx.put("k1", "v1_mutated");
tx.put("k2", "v2_temp");
assert_eq!(engine.get("k1"), Some(&"v1_mutated".to_string()));
} // tx goes out of scope -> Drop performs automatic rollback
assert_eq!(engine.get("k1"), Some(&"v1_initial".to_string()));
assert_eq!(engine.get("k2"), None);
assert_ne!(engine.get("k1"), Some(&"v1_mutated".to_string()));
}
#[test]
fn test_commit_result_matches() {
let mut engine = StorageEngine::new();
let tx = engine.begin_transaction(303);
let commit_res = tx.commit();
assert!(matches!(commit_res, Ok(())));
}
}
Technical Explanation
- Exception / Early-Return Safety via Destructors: In systems programming, operations often exit prematurely due to IO errors or validation failures. By placing rollback logic in
Drop::drop, rollback execution is guaranteed by the Rust runtime regardless of how control flows out of the block. - Disarming Drop Guards with State Flags vs
ManuallyDrop: Thecommitted: boolflag pattern allows disarming the cleanup guard oncecommit()successfully finishes. Alternatively,std::mem::ManuallyDroporstd::mem::forgetcan be used to preventDrop::dropfrom executing. The state flag approach is safer and does not require unsafe code. - Borrow Checker Lifetimes (
'a):Transaction<'a>holds an exclusive mutable reference&'a mut StorageEngine. This enforces compile-time isolation: while a transaction is active, no other part of the application can mutate or begin another transaction on the sameStorageEngine, preventing concurrent data races. - Edge Cases & Rollback Invariants:
- First-Write Wins Snapshot:
putchecksif !self.original_snapshots.contains_key(&key)to record snapshot state only on the first mutation of a key during a transaction. Subsequent mutations in the same transaction do not overwrite the true initial value. - Key Deletion on Rollback: Keys that did not exist prior to the transaction store
Noneinoriginal_snapshots. During rollback,Nonecausesself.engine.data.remove(&key), completely purging uncommitted additions.
- First-Write Wins Snapshot:
Exercise 3: Zero-Copy Custom Arena Slot Lease Guard with Atomic Ref-Counted Drop Reclamation
Scenario:
High-performance network packet parsers and real-time game engines minimize heap allocations by renting slots from fixed-size arena memory pools. Multiple components can hold a shared lease to a single slot.
Design an ArenaSlotPool<T> and a reference-counted handle guard SlotLease<T>.
SlotLease<T>represents a leased reference to a slot index in the arena pool. CloningSlotLeaseatomically increments anAtomicUsizereference counter on the target slot.- When a
SlotLeaseis dropped,Drop::dropatomically decrements the slot's reference count usingfetch_sub. - When the reference count reaches 0,
Dropautomatically clears the slot data, increments the slot's generation counter (to prevent stale ABA handles), and returns the slot index to the free list. - Provide an
invalidate(&mut self)method onSlotLeasethat marks the lease invalid, forcing immediate slot purging on drop.
Requirements:
- Implement
ArenaSlotPool<T>,SlotData<T>, andSlotLease<T>. - Implement
CloneandDropforSlotLease<T>. - Write unit tests validating lease allocation, reference counting across clones, immediate slot invalidation, and slot reuse after drop.
Answer
Implementation
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
#[derive(Debug, PartialEq, Eq)]
pub enum ArenaError {
SlotExhausted,
InvalidSlot,
}
pub struct SlotData<T> {
pub value: Option<T>,
pub generation: u64,
pub ref_count: AtomicUsize,
}
pub struct ArenaPoolInner<T> {
pub slots: Vec<SlotData<T>>,
pub free_indices: Vec<usize>,
}
#[derive(Clone)]
pub struct ArenaSlotPool<T> {
inner: Arc<Mutex<ArenaPoolInner<T>>>,
}
impl<T: Clone> ArenaSlotPool<T> {
pub fn new(capacity: usize) -> Self {
let mut slots = Vec::with_capacity(capacity);
let mut free_indices = Vec::with_capacity(capacity);
for i in 0..capacity {
slots.push(SlotData {
value: None,
generation: 0,
ref_count: AtomicUsize::new(0),
});
free_indices.push(capacity - 1 - i);
}
Self {
inner: Arc::new(Mutex::new(ArenaPoolInner { slots, free_indices })),
}
}
pub fn alloc(&self, value: T) -> Result<SlotLease<T>, ArenaError> {
let mut guard = self.inner.lock().unwrap();
if let Some(index) = guard.free_indices.pop() {
let slot = &mut guard.slots[index];
slot.value = Some(value);
slot.ref_count.store(1, Ordering::SeqCst);
let gen = slot.generation;
Ok(SlotLease {
pool: self.clone(),
index,
generation: gen,
is_valid: true,
})
} else {
Err(ArenaError::SlotExhausted)
}
}
pub fn active_leases(&self) -> usize {
let guard = self.inner.lock().unwrap();
guard.slots.len() - guard.free_indices.len()
}
}
pub struct SlotLease<T: Clone> {
pool: ArenaSlotPool<T>,
index: usize,
generation: u64,
is_valid: bool,
}
impl<T: Clone> SlotLease<T> {
pub fn get(&self) -> Option<T> {
if !self.is_valid {
return None;
}
let guard = self.pool.inner.lock().unwrap();
let slot = &guard.slots[self.index];
if slot.generation == self.generation {
slot.value.clone()
} else {
None
}
}
pub fn invalidate(&mut self) {
self.is_valid = false;
}
pub fn slot_index(&self) -> usize {
self.index
}
}
impl<T: Clone> Clone for SlotLease<T> {
fn clone(&self) -> Self {
let guard = self.pool.inner.lock().unwrap();
let slot = &guard.slots[self.index];
slot.ref_count.fetch_add(1, Ordering::SeqCst);
Self {
pool: self.pool.clone(),
index: self.index,
generation: self.generation,
is_valid: self.is_valid,
}
}
}
impl<T: Clone> Drop for SlotLease<T> {
fn drop(&mut self) {
let mut guard = self.pool.inner.lock().unwrap();
let slot = &mut guard.slots[self.index];
if slot.generation != self.generation {
return;
}
if !self.is_valid {
slot.ref_count.store(0, Ordering::SeqCst);
slot.value = None;
slot.generation += 1;
guard.free_indices.push(self.index);
return;
}
let prev = slot.ref_count.fetch_sub(1, Ordering::SeqCst);
if prev == 1 {
slot.value = None;
slot.generation += 1;
guard.free_indices.push(self.index);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_arena_alloc_and_auto_drop_reclaim() {
let arena = ArenaSlotPool::new(2);
assert_eq!(arena.active_leases(), 0);
{
let lease1 = arena.alloc("packet_data_1").unwrap();
assert_eq!(lease1.get(), Some("packet_data_1"));
assert_eq!(arena.active_leases(), 1);
{
let lease2 = arena.alloc("packet_data_2").unwrap();
assert_eq!(lease2.get(), Some("packet_data_2"));
assert_eq!(arena.active_leases(), 2);
assert_ne!(lease1.slot_index(), lease2.slot_index());
} // lease2 drops here -> slot reclaimed
assert_eq!(arena.active_leases(), 1);
} // lease1 drops here -> slot reclaimed
assert_eq!(arena.active_leases(), 0);
}
#[test]
fn test_cloned_lease_ref_counting() {
let arena = ArenaSlotPool::new(1);
let l1 = arena.alloc(42).unwrap();
let l2 = l1.clone();
assert_eq!(arena.active_leases(), 1);
drop(l1); // Decrements ref_count to 1, slot remains allocated
assert_eq!(arena.active_leases(), 1);
assert_eq!(l2.get(), Some(42));
drop(l2); // Decrements ref_count to 0 -> slot reclaimed
assert_eq!(arena.active_leases(), 0);
}
#[test]
fn test_invalidate_immediate_reclaim() {
let arena = ArenaSlotPool::<&str>::new(1);
let mut l1 = arena.alloc("critical").unwrap();
l1.invalidate();
assert_eq!(l1.get(), None);
drop(l1);
assert_eq!(arena.active_leases(), 0);
let res = arena.alloc("new_critical");
assert!(matches!(res, Ok(_)));
}
}
Technical Explanation
- Arena Allocation & Generation Counter Lifecycles: Slots are pre-allocated in memory. When a slot is leased out, a generation counter tracks the version of the lease. When a slot is reclaimed and returned to
free_indices, its generation is incremented (generation += 1). If a dangling or outdated handle attempts to access a slot, generation mismatch prevents use-after-free bugs. - Atomic Reference Counting in
Drop:SlotLease::clone()incrementsref_countviafetch_add(1, Ordering::SeqCst).SlotLease::drop()decrementsref_countviafetch_sub(1, Ordering::SeqCst). Becausefetch_subreturns the previous value,prev == 1signifies that the current dropped instance was the last active lease holding the slot. - ABA Prevention & Slot Reclaiming Invariants: When
prev == 1,Dropatomically clearsslot.value = None, bumpsgeneration, and pushesself.indexback intofree_indices. Any subsequent allocation reusingself.indexreceives a higher generation number, rendering stale references harmless. - Edge Cases & Concurrency Considerations:
- Manual Invalidation: Calling
invalidate(&mut self)setsis_valid = false. UponDrop::drop, the branch detects invalidation, resetsref_countdirectly to 0, clears payload, bumps generation, and recycles the index immediately. - Sequential Consistency: Using
Ordering::SeqCstensures atomic mutations onref_countare globally visible across thread boundaries before slot index updates occur in the free list.
- Manual Invalidation: Calling
6. Related Terms
- Ownership — The system that decides when the scope ends and
Dropis called. CopyTrait — As a rule, types that implementCopyare not allowed to implementDrop(you can't trivially duplicate something that requires complex cleanup!).Drop Check(dropck) — Related concept:Drop Check(dropck).std::memUtilities (replace,take,swap,drop) — Related concept:std::memUtilities (replace,take,swap,drop).panic!— Related concept:panic!.- Memory Leaks & Reference Cycles — Related concept: Memory Leaks & Reference Cycles.
- RAII (Resource Acquisition Is Initialization) — Related concept: RAII (Resource Acquisition Is Initialization).
7. Key Takeaways
- The
Droptrait allows you to define custom cleanup code (a "destructor") for a type. - It is called automatically by the compiler the exact millisecond a variable goes out of scope.
- Variables are dropped in the reverse order of their creation (Last In, First Out).
- You cannot call
.drop()manually, but you can force an early cleanup usingstd::mem::drop(var). - You rarely need to implement this yourself. Rust automatically drops all fields inside a struct for you.