C+
Ownership & safety · View as Markdown

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.

Loop bodies drop each iteration

A value created inside a while, for, or loop body is its own scope: it drops at the end of every iteration, and on break or continue, not once at the end of the loop. So a buffer built per iteration is released per iteration, with no accumulation.

let mut i: i32 = 0;
while i < n {
    let line: Text = read_line();     // line.drop() runs at the end of
    handle(line);                     //   every iteration, including on continue
    i = i +% 1;
}

A binding declared outside the loop is different: moving it on each iteration is rejected (E0335), because after the first iteration moved it away there is nothing left to move. Re-initialize it before the move if you need that pattern.

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.