AppendixFFI Workflows

Callbacks

Callback signatures, `RawPtr` user data, and the current native/hybrid callback rules.

Callbacks are a supported part of the current FFI design.

Current Rules

  • callback typedefs lower to real native function pointers
  • callback arguments can be passed directly to native or extern functions
  • callback targets must currently resolve to @Native functions or extern callback symbols
  • user data/context is passed explicitly as RawPtr
  • captured closures do not cross the ABI boundary
  • Kira-owned persistent callback state uses nativeState, nativeUserData, and nativeRecover(any)

Minimal Example

From examples/callbacks/app/main.kira:

import callbacks as cb

struct CounterState {
    var count: Int
    var total: Int
}

@Main
function main() {
    print("callbacks")

    var state = nativeState(CounterState {
        count: 0
        total: 0
    })
    var token = nativeUserData(state)

    print(run_native(token, 5))
    print(run_native(token, 7))

    var recovered = nativeRecover(token)
    print(recovered.count)
    print(recovered.total)
    return
}

@Native
function run_native(user_data: RawPtr, value: I64) -> I64 {
    return cb.kira_invoke_callback(add_and_track, user_data, value)
}

@Native
function add_and_track(value: I64, user_data: RawPtr) -> I64 {
    var state = nativeRecover(user_data)
    state.count = state.count + 1
    state.total = state.total + value
    return value + state.count
}

That flow matters because it proves a real opaque-userdata contract:

  • Kira owns the callback state storage
  • native code only carries a RawPtr token
  • the callback recovers the original typed state instead of reaching for globals
  • repeated callback invocations mutate the same persistent stored state

It is backed by a small C header and implementation:

  • a callback typedef
  • a function that accepts the callback and invokes it

Signature Checking

Kira validates callback signatures at semantic-analysis time.

If the target function does not match the required callback type exactly, the repo currently raises:

  • KSEM045
  • title: invalid callback signature

The checks include:

  • parameter count
  • parameter types
  • result type
  • execution compatibility

Runtime functions cannot currently be converted into native callback targets.

Native And Hybrid Support

The callback path is proved in both:

  • native-only execution
  • hybrid execution

That is why the repo carries both:

  • tests/pass/run/ffi_callback_native
  • tests/pass/run/ffi_callback_hybrid
  • tests/pass/run/ffi_callback_state_parity

For hybrid mode, the callback still crosses a native boundary. The runtime/native bridge handles marshalling around that call path.

On this page