Skip to content
FutureBlitz

    Install

    Null safety and memory, 3 of 5

    Weak references

    Break reference cycles with Weak<T> and [Weak Self], so objects that point at each other are freed.

    What you’ll build

    A folder tree in which each folder knows its parent and its children, and a button whose click handler calls back into the object that owns it. Both make objects that refer to each other, and both use weak references so that they’re still freed.

    A cycle never reaches zero

    When two objects refer to each other, each keeps the other’s count above zero, so neither is ever freed, even once nothing else refers to them. That’s a leak. A debug build reports it when the program ends, as here, where Destroy never runs for either node.

    cycle.fb
    Import std.core
    
    Class Node
        ReadOnly Field Name: String
        Field Next: Node?
    
        New(name: String)
            Name = name
        End New
    
        Method Destroy()
            Print("free {Name}")
        End Method
    End Class
    
    Local a = Node("a")
    Local b = Node("b")
    a.Next = b
    b.Next = a
    Print("done")
    Output
    done
    leak: 2 Node objects were never released
    leak: a cycle of 2 objects: Node.Next -> Node.Next -> the first Node

    Weak

    A Weak<Folder> field refers to a Folder without counting as a reference to it. It’s assigned a Folder as usual, and reading it gives a Folder?, which is Null once the Folder has been freed. A tree’s children hold strong references, and each child’s link to its parent is weak, so the tree has no cycle.

    folders.fb
    Import std.core
    
    Class Folder
        ReadOnly Field Name: String
        Field Parent: Weak<Folder>
        Field Children: Folder[] = []
    
        New(name: String)
            Name = name
        End New
    
        Method Add(child: Folder)
            Children.Add(child)
            child.Parent = Self
        End Method
    
        Method Path(): String
            Local parent = Parent
            If parent == Null Then Return Name
            Return parent.Path() + "/" + Name
        End Method
    
        Method Destroy()
            Print("free {Name}")
        End Method
    End Class
    
    Local home = Folder("home")
    Local docs = Folder("docs")
    home.Add(docs)
    Local notes = Folder("notes")
    docs.Add(notes)
    Print(notes.Path())
    
    home = Folder("elsewhere")
    Print("docs' parent: {docs.Parent?.Name ?? "gone"}")
    Print(docs.Path())
    Output
    home/docs/notes
    free home
    docs' parent: gone
    docs
    free docs
    free notes
    free elsewhere

    When home takes a new Folder, the old one’s only strong reference is gone, so it’s freed, and docs.Parent reads as Null. A weak reference’s object can go at any time, so a check doesn’t narrow it: Path reads Parent into a Local and checks that.

    [Weak Self] in a lambda

    A lambda that uses its object’s fields or Methods captures Self, and holds a reference to it. Stored in a field of that object, or of an object it owns, the lambda and the object keep each other alive. A capture list, Function[Weak Self](), captures Self weakly instead, and inside the lambda Self?. reaches the object while it exists.

    button.fb
    Import std.core
    
    Class Button
        Field OnClick: (Func())?
    
        Method Click()
            Local handler = OnClick
            If handler != Null Then handler()
        End Method
    
        Method Destroy()
            Print("free the button")
        End Method
    End Class
    
    Class Counter
        Field clicks: Int
        Field button: Button
    
        New()
            button = Button()
            button.OnClick = Function[Weak Self]()
                Self?.Bump()
            End Function
        End New
    
        Method Bump()
            clicks++
            Print("clicks: {clicks}")
        End Method
    
        Method Press()
            button.Click()
        End Method
    
        Method Destroy()
            Print("free the counter")
        End Method
    End Class
    
    Local counter = Counter()
    counter.Press()
    counter.Press()
    Output
    clicks: 1
    clicks: 2
    free the counter
    free the button

    Without [Weak Self], both objects and the lambda would leak. fb warns about the plainest case, a lambda that captures Self stored in a field of Self, and suggests [Weak Self].

    What you learned

    • Objects that refer to each other in a cycle are never freed: a leak.
    • A Weak<T> refers to an object without keeping it alive, and reads as a T?.
    • A tree holds its children strongly and its parent weakly.
    • A weak reference isn’t narrowed by a check; read it into a Local first.
    • Function[Weak Self]() keeps a stored lambda from keeping its object alive.

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