Skip to content
FutureBlitz

    Install

    Null safety and memory, 4 of 5

    Defer

    Schedule cleanup to run when a block ends, however it ends.

    What you’ll build

    Three small programs that clean up after themselves: one that opens a connection and closes it on every path out of a function, an early Return included; one that sets up and takes down in the right order; and one that reports how far its work got. Defer puts the cleanup right after the code that needs it, so it can’t be forgotten when the code gains another way out.

    Run something at the end of a block

    Defer takes one statement, and runs it when the block it’s in ends: at its last line, or through Return, Break, Continue, or an error. Here the connection is closed on both paths out of Fetch.

    connection.fb
    Import std.core
    
    Class Connection
        ReadOnly Field Name: String
    
        New(name: String)
            Name = name
            Print("open {name}")
        End New
    
        Method Close()
            Print("close {Name}")
        End Method
    End Class
    
    Function Fetch(name: String, cached: Bool): String
        Local connection = Connection(name)
        Defer connection.Close()
        If cached Then Return "(from the cache)"
        Print("reading from {name}")
        Return "the data from {name}"
    End Function
    
    Print(Fetch("db.example", False))
    Print(Fetch("db.example", True))
    Output
    open db.example
    reading from db.example
    close db.example
    the data from db.example
    open db.example
    close db.example
    (from the cache)

    The value to return is worked out before the deferred statement runs, so a Defer can’t change what a function returns.

    Several Defers

    A block’s Defers run in reverse order, the last one first, so what was set up last is taken down first. A loop’s body is a block, so each pass runs its own Defers at its end.

    setup.fb
    Import std.core
    
    Function Setup()
        Print("connect")
        Defer Print("disconnect")
        Print("log in")
        Defer Print("log out")
        Print("work")
    End Function
    
    Setup()
    
    For pass = 1 To 2
        Defer Print("end of pass {pass}")
        Print("pass {pass}")
    End For
    Output
    connect
    log in
    work
    log out
    disconnect
    pass 1
    end of pass 1
    pass 2
    end of pass 2

    Values as they are at the end

    A deferred statement’s values are read when it runs, not when the Defer is reached. Here it shows count as it is when the block ends.

    process.fb
    Import std.core
    
    Function Process(items: String[])
        Local count = 0
        Defer Print("processed {count} of {items.Count}")
        For item In items
            If item == "" Then Return
            count++
        End For
    End Function
    
    Process(["a", "b", "c"])
    Process(["a", "", "c"])
    Output
    processed 3 of 3
    processed 1 of 3

    A Defer runs on errors too, which Finally and Defer shows. It doesn’t run on a panic, which ends the program at once.

    What you learned

    • Defer statement runs the statement when its block ends, however it ends.
    • A block’s Defers run the last first, and each pass of a loop runs its own.
    • A deferred statement reads its values when it runs.
    • The returned value is worked out before the Defers run.
    • A panic ends the program without running Defers.

    Report an issue with this page or with FutureBlitz on GitHub.