Drop & defer
Drop: your destructor
A struct that defines a method literally named drop runs that method on scope exit. The signature is fixed: fn drop(mut self), with no return type.
struct Buf { ptr: *u8, len: usize }
impl Buf {
fn drop(mut self) {
unsafe { free(self.ptr); }
}
}
fn main() -> i32 {
let b: Buf = make_buf();
// ... use b ...
return 0;
} // b.drop() runs here
Defining drop makes the type non-Copy, which is necessary: copying a value that owns a resource would lead to a double free. See Ownership for how Copy is derived.
Raw-pointer accountability
A drop frees its struct's fields by hand; the compiler does not synthesize per-field drops. For a raw-pointer field (*T) the compiler cannot tell whether the struct owns the memory or only borrows it, so it makes you say which. A raw-pointer field that is neither released by the struct's drop nor marked opaque is a compile error (E0510). There is no silent-leak default.
struct Buf { ptr: *u8, len: usize }
impl Buf {
fn drop(mut self) { unsafe { free(self.ptr); } } // owned: you free what you own
}
struct View { opaque ptr: *u8, len: usize } // borrowed: `opaque` means "not mine"
Severity tracks what the compiler can prove about the drop body. It is a structural check, with no dataflow:
The field's release in drop is... |
Result |
|---|---|
| unconditional, or guarded only by a null-test on the same field | clean |
| present but conditional (a refcount, flag, or loop, so it cannot be proven to always run) | W0002 warning |
absent, delegated to a helper, or there is no drop at all |
E0510 error |
the field is marked opaque |
clean (managed elsewhere) |
free(self.ptr as *u8) counts as releasing ptr, since the cast is transparent. The warning case is for legitimately conditional owners such as refcounted types, which free their control block only on the last reference. Use opaque only when another owner truly frees the pointer: an FFI handle the runtime owns, a borrowed view, or a pointer freed by a sibling struct.
Containers drop their elements
The hand-freeing above is only for the raw-pointer fields you own directly. For the owning containers — Vec[T], Box[T], Arc[T], Rc[T], HashMap[K, V] — drop is automatic and recursive. Dropping a Vec[T] runs each element's drop exactly once and then frees the backing buffer, so a Vec[Buf] releases every Buf before the buffer goes. You write no per-element loop.
fn main() -> i32 {
let mut v: Vec[Buf] = Vec::new();
v.push(make_buf());
v.push(make_buf());
return 0;
} // each Buf.drop() runs, then v frees its buffer
Enum payloads drop when consumed
Matching an owned enum and binding a payload you do not move out drops that payload at the arm's exit, so nothing leaks:
match msg { // msg is owned
Message::Text(s) => println(s.len() as i32), // s drops at arm exit
Message::Close => {}
}
Every move-out shape still disarms that drop, so a payload you do move out is not dropped twice.
defer: run at scope exit, LIFO
defer schedules an action to run when the scope exits, in last-in-first-out order:
fn main() -> i32 {
println(1);
defer println(4);
defer println(3);
println(2);
return 0;
}
// Prints 1, 2, 3, 4
defer and Drop share one scope-exit stack: they interleave in declaration order and are popped LIFO at exit.