Skip to content
FutureBlitz

    Install

    C interop, 5 of 7

    Callbacks

    Hand C a function or a lambda to call back, as qsort's comparison, and call a C function pointer yourself.

    What you’ll build

    An array of numbers sorted by C’s qsort, which calls back into FutureBlitz code to compare two of them: first with a named function, then with a lambda that sorts the other way. Then a function that takes a Callback and calls it itself.

    A function C calls

    Callback(A, B): R is a C function pointer, R (*)(A, B). Where one is expected, a top-level function whose parameters and result are exactly its types can be passed by name, unless it’s generic, Async, or Unsafe, throws, or takes a parameter ByRef; a Method can’t be one. qsort sorts any array, so it passes the comparison two void*s, which the function reads as Int32s.

    Import std.core
    
    Extern "C"
        Function qsort(items: Ptr<Void>, count: UInt64, size: UInt64,
                       compare: Callback(Ptr<Void>, Ptr<Void>): Int32)
    End Extern
    
    Function Ascending(a: Ptr<Void>, b: Ptr<Void>): Int32
        Unsafe
            Local x = (a As Ptr<Int32>).Value
            Local y = (b As Ptr<Int32>).Value
            Return x < y ? -1 : (x > y ? 1 : 0)
        End Unsafe
    End Function
    
    Local numbers: Int32[] = [42, 7, 19, 3]
    Unsafe
        qsort(AddressOf(numbers[0]), 4, 4, Ascending)
    End Unsafe
    Print(numbers)
    Output
    [3, 7, 19, 42]

    AddressOf(numbers[0]) is the address of the array’s first element, and its elements follow it in memory, so qsort sorts the array in place. Reading what the pointers point at is Unsafe, so Ascending does it in an Unsafe block.

    A lambda C calls

    A lambda works too, when it has exactly the Callback’s types and uses nothing from around it. This one sorts from largest to smallest.

    Import std.core
    
    Extern "C"
        Function qsort(items: Ptr<Void>, count: UInt64, size: UInt64,
                       compare: Callback(Ptr<Void>, Ptr<Void>): Int32)
    End Extern
    
    Local numbers: Int32[] = [42, 7, 19, 3]
    Unsafe
        qsort(AddressOf(numbers[0]), 4, 4, Function(a: Ptr<Void>, b: Ptr<Void>): Int32
            Local x = (a As Ptr<Int32>).Value
            Local y = (b As Ptr<Int32>).Value
            Return y - x
        End Function)
    End Unsafe
    Print(numbers)
    Output
    [42, 19, 7, 3]

    A lambda made in Unsafe code is Unsafe code too, so it reads the pointers without a block of its own.

    Nothing from around it

    C passes a callback only its arguments, so a lambda that uses a Local from around it can’t be one. A lambda that reads a descending Local is an error that says why:

    Import std.core
    
    Extern "C"
        Function qsort(items: Ptr<Void>, count: UInt64, size: UInt64,
                       compare: Callback(Ptr<Void>, Ptr<Void>): Int32)
    End Extern
    
    Local numbers: Int32[] = [42, 7, 19, 3]
    Local descending = True
    Unsafe
        qsort(AddressOf(numbers[0]), 4, 4, Function(a: Ptr<Void>, b: Ptr<Void>): Int32
            Local x = (a As Ptr<Int32>).Value
            Local y = (b As Ptr<Int32>).Value
            Return descending ? y - x : x - y
        End Function)
    End Unsafe
    Print(numbers)
    $ fb run main.fb
       Compiling main.fb
    main.fb:11:40: error: a Callback's lambda can't use 'descending' from around it, since C passes it only its arguments; a Ptr<Void> C passes along can carry what it needs
       |
    11 |     qsort(AddressOf(numbers[0]), 4, 4, Function(a: Ptr<Void>, b: Ptr<Void>): Int32
       |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    error: couldn't build main.fb because of 1 error

    C libraries that call back usually take a void* of “user data” for this, which they pass to the callback unchanged.

    Calling a Callback

    FutureBlitz code calls a Callback like a function, from Unsafe code, since it’s a raw pointer that could be anything C gave.

    Import std.core
    
    Function Twice(x: Int32): Int32 => x * 2
    
    Function Apply(f: Callback(Int32): Int32, value: Int32): Int32
        Unsafe
            Return f(value)
        End Unsafe
    End Function
    
    Print(Apply(Twice, 21))
    Output
    42

    A Callback’s zero is Null, and a debug build panics when a Null one is called. C can call a Callback on any thread, so the compiler checks its code as it does code on another thread, and a Callback can’t throw.

    What you learned

    • Callback(A, B): R is a C function pointer, which a top-level function of exactly those types can be passed as.
    • A lambda can be a Callback when it captures nothing.
    • Data a callback needs comes through a Ptr<Void> that C passes along.
    • Only Unsafe code calls a Callback.

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