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)[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)[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)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))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): Ris 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.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.