Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Defer and Cleanup

Flint provides two cleanup mechanisms:

  1. Implicit drop: values are automatically cleaned up when they go out of scope.
  2. defer: explicit cleanup statements that run at scope exit.

defer

defer schedules a call to run when the enclosing scope exits. It pairs naturally with resource acquisition:

let mut file = io.open("log.txt")?
defer file.close()

// ... use file ...
// file.close() runs here, even on early return or error propagation
let lock = mutex.lock()
defer lock.release()

shared_count += 1
// lock.release() runs here

Execution Order

Deferred calls run in LIFO order (last deferred, first run):

defer cleanup_a()
defer cleanup_b()
defer cleanup_c()
// runs: cleanup_c(), cleanup_b(), cleanup_a()

When Deferred Calls Run

  • Normal scope exit
  • return
  • break
  • Error propagation via ?
fn process(path: string) -> Result<(), Error> {
    let mut file = io.open(path)?
    defer file.close()         // runs whether we return Ok or Err

    let data = file.read_all()?   // if this returns Err, file.close() still runs
    return parse(data)
}

Arguments Are Captured at the defer Site

The arguments to a deferred call are evaluated immediately when defer is reached, not when it runs:

let pin_num: u32 = 25
defer log.info(pin_num)   // captures 25 now

let pin_num: u32 = 26     // error: same-scope redeclaration

defer and Scope

defer is scoped to the block it appears in, not the function:

{
    let lock = mutex.lock()
    defer lock.release()
    write_shared_state()
}
// lock.release() ran when the block exited

do_other_work()    // lock is already released

This makes defer useful for short-lived critical sections without a helper function.

Implicit Drop

Values are automatically dropped at the end of their scope in reverse initialization order:

{
    let a = Resource.acquire("a")
    let b = Resource.acquire("b")
    let c = Resource.acquire("c")
}
// c drops first, then b, then a

Struct fields drop in reverse declaration order. Array elements drop from highest to lowest index.

Flint does not support user-defined Drop traits. Drop glue is generated automatically by the compiler for any type that holds owned resources.

Deferred Calls vs. Implicit Drop

defer runs before implicit drop of remaining locals in the same scope:

let conn = db.connect()?
defer conn.close()     // 1. runs first

let cache = Cache.new()
// 2. cache drops here (implicit)
// 3. conn drops after defer

Conventions

  • Use defer to pair cleanup with acquisition: open/close, lock/release, start/stop.
  • Keep deferred calls simple and infallible when possible.
  • Do not use defer as a general exception or error-handling mechanism. That is what Result is for.
// Good: symmetric resource management
let mut port = uart.open(0)?
defer port.close()

// Good: scoped lock
{
    let guard = mutex.lock()
    defer guard.release()
    modify_shared()
}

Defer Blocks

When you need to group multiple cleanup steps together, defer accepts a block:

let lock = mutex.lock()
let handle = resource.acquire()

defer {
    handle.release()
    lock.unlock()
    log.info("cleanup complete")
}

Statements inside the block run together in the order they appear, as a single deferred action. This is cleaner than multiple separate defer statements when the cleanup steps are logically related and their ordering within the group matters more than LIFO sequencing.

The block form and the expression form follow the same rules: the block executes at scope exit, on return, break, and error propagation via ?.