Skip to content
FutureBlitz

    Install

    Null safety and memory, 2 of 5

    Reference counting and lifetimes

    See exactly when an object is freed, and use Destroy to clean up as it goes.

    What you’ll build

    A Connection Class that says when it opens and when it closes, used in a few ways: inside a function, shared between variables, and kept in an array. Its messages show when each object is freed, which in FutureBlitz is a fixed point in the program, not whenever a collector runs.

    Freed when the last reference goes

    Every object counts the references to it: variables, fields, array elements, and captures. When the count reaches zero, the object is freed at once, and its Destroy Method runs first, if it has one. A Local’s reference goes when its block ends, so an object made in a function and not kept is freed when the function returns.

    connections.fb
    Import std.core
    
    Class Connection
        ReadOnly Field Name: String
    
        New(name: String)
            Name = name
            Print("open {name}")
        End New
    
        Method Destroy()
            Print("close {Name}")
        End Method
    End Class
    
    Function Query()
        Local connection = Connection("query")
        Print("using {connection.Name}")
    End Function
    
    Query()
    Print("after Query")
    Output
    open query
    using query
    close query
    after Query

    There’s no Delete: code can’t free an object while something still refers to it, so no reference ever points at freed memory. Only the raw pointers of Unsafe code, used with C libraries, can.

    Shared objects live as long as any reference

    Two variables that share an object each hold a reference. Giving one of them a new object takes away only its own reference, and the old object stays while the other still holds it.

    connections.fb
    Import std.core
    
    Class Connection
        ReadOnly Field Name: String
    
        New(name: String)
            Name = name
            Print("open {name}")
        End New
    
        Method Destroy()
            Print("close {Name}")
        End Method
    End Class
    
    Local primary = Connection("first")
    Local backup = primary
    primary = Connection("second")
    Print("primary replaced")
    backup = Connection("third")
    Print("backup replaced")
    Print("end of program")
    Output
    open first
    open second
    primary replaced
    open third
    close first
    backup replaced
    end of program
    close third
    close second

    first closes only when backup, its last reference, takes a new object. When the program ends, its variables let go of what they hold, the last declared first, which closes third, then second.

    Collections hold references too

    An array, a Map, or any collection holds a reference to each object in it. Removing an object, or clearing the collection, lets it go.

    connections.fb
    Import std.core
    
    Class Connection
        ReadOnly Field Name: String
    
        New(name: String)
            Name = name
        End New
    
        Method Destroy()
            Print("close {Name}")
        End Method
    End Class
    
    Local pool = [Connection("a"), Connection("b"), Connection("c")]
    pool.RemoveAt(0)
    Print("removed a")
    Local kept = pool[0]
    pool.Clear()
    Print("cleared, still holding {kept.Name}")
    Output
    close a
    removed a
    close c
    cleared, still holding b
    close b

    Destroy for cleanup

    Since the moment an object goes is known, Destroy is a reliable place to give back what the object holds, like a file, a socket, or a lock. It takes no parameters, can’t be called directly, and runs on every object of its Class. A Class that extends another runs its own Destroy first, then the other’s.

    connections.fb
    Import std.core
    
    Class Connection
        ReadOnly Field Name: String
    
        New(name: String)
            Name = name
        End New
    
        Method Destroy()
            Print("close the connection {Name}")
        End Method
    End Class
    
    Class SecureConnection Extends Connection
        Method Destroy()
            Print("end the session of {Name}")
        End Method
    End Class
    
    Local link = SecureConnection("bank")
    link = SecureConnection("shop")
    Print("done")
    Output
    end the session of bank
    close the connection bank
    done
    end the session of shop
    close the connection shop

    Destroy is never marked Override: every Class’s own runs.

    What you learned

    • An object is freed the moment its last reference goes, and its Destroy runs then.
    • Variables, fields, collections, and closures all hold references.
    • There’s no Delete, so outside Unsafe code no reference can point at a freed object.
    • Destroy cleans up what an object holds; a Class and the Class it extends each run their own, the most derived first.

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