5.2.4. C Integration: Callbacks and Closures

This tutorial shows how C code can receive and invoke daslang callable types: function pointers, lambdas, and blocks.

5.2.4.1. Three callable types

daslang has three callable types, each with different semantics:

Type

Mangled

Characteristics

function pointer

@@

No capture, cheapest, like a C function pointer

lambda

@

Heap-allocated, captures variables by reference

block

$

Stack-allocated, captures, cannot outlive scope

The mangled signature encodes the callable type after the argument list:

  • 0<i;f>@@ — function pointer taking (int, float)

  • 0<i;f>@ — lambda taking (int, float)

  • 0<i;f>$ — block taking (int, float)

A leading return type is prepended: "s 0<i;f>@@ i f" means returns string, takes function<(int,float):string>, int, float.

5.2.4.2. Calling a function pointer

vec4f c_call_function(das_context * ctx, das_node * node, vec4f * args) {
    das_function * callback = das_argument_function(args[0]);
    int a    = das_argument_int(args[1]);
    float b  = das_argument_float(args[2]);

    vec4f cb_args[2];
    cb_args[0] = das_result_int(a);
    cb_args[1] = das_result_float(b);

    vec4f ret = das_context_eval_with_catch(ctx, callback, cb_args);
    return ret;
}

From daslang, pass a function pointer with @@:

def format_result(a : int; b : float) : string {
    return "formatted: {a} + {b}"
}

let r = c_call_function(@@format_result, 10, 2.5)

5.2.4.3. Calling a lambda

Lambdas carry captured state. When calling from C, the first argument must be the lambda itself (its capture block):

vec4f c_call_lambda(das_context * ctx, das_node * node, vec4f * args) {
    das_lambda * lambda = das_argument_lambda(args[0]);
    int a    = das_argument_int(args[1]);
    float b  = das_argument_float(args[2]);

    // First arg = lambda capture, then actual arguments.
    vec4f lmb_args[3];
    lmb_args[0] = das_result_lambda(lambda);
    lmb_args[1] = das_result_int(a);
    lmb_args[2] = das_result_float(b);

    vec4f ret = das_context_eval_lambda(ctx, lambda, lmb_args);
    return ret;
}

Important

das_context_eval_lambda requires the lambda as lmb_args[0]. Omitting it will crash or produce garbage results.

From daslang:

var counter = 0
var lmb <- @(a : int; b : float) : string {
    counter += a
    return "lambda: a={a} b={b} counter={counter}"
}
let r = c_call_lambda(lmb, 5, 1.5)

5.2.4.4. Calling a block

Blocks are lighter than lambdas — they live on the stack and cannot outlive the scope that created them. Unlike lambdas, block arguments do not include the block itself:

vec4f c_call_block(das_context * ctx, das_node * node, vec4f * args) {
    das_block * block = das_argument_block(args[0]);
    int    a = das_argument_int(args[1]);
    float  b = das_argument_float(args[2]);

    vec4f blk_args[2];
    blk_args[0] = das_result_int(a);
    blk_args[1] = das_result_float(b);

    vec4f ret = das_context_eval_block(ctx, block, blk_args);
    return ret;
}

From daslang:

var blk <- $(a : int; b : float) : string {
    counter += a
    return "block: a={a} b={b} counter={counter}"
}
let r = c_call_block(blk, 7, 0.5)

5.2.4.5. Mangled signature cheat sheet

"s 0<i;f>@@ i f"   →  function pointer callback
"s 0<i;f>@  i f"   →  lambda callback
"s 0<i;f>$  i f"   →  block callback

5.2.4.6. Building and running

cmake --build build --config Release --target integration_c_04
bin\Release\integration_c_04.exe

Expected output:

--- function pointer callback ---
[C] calling function pointer with (10, 2.50)
[C] callback returned: "formatted: 10 + 2.5"
C returned: formatted: 10 + 2.5

--- lambda callback ---
[C] calling lambda with (5, 1.50)
[C] lambda returned: "lambda: a=5 b=1.5 counter=5"
C returned: lambda: a=5 b=1.5 counter=5
[C] calling lambda with (3, 0.50)
[C] lambda returned: "lambda: a=3 b=0.5 counter=8"
C returned: lambda: a=3 b=0.5 counter=8

--- block callback ---
[C] calling block with (7, 0.50)
[C] block returned: "block: a=7 b=0.5 counter=7"
C returned: block: a=7 b=0.5 counter=7
[C] calling block with (3, 1.50)
[C] block returned: "block: a=3 b=1.5 counter=10"
C returned: block: a=3 b=1.5 counter=10

See also

Full source: 04_callbacks.c, 04_callbacks.das

Previous tutorial: tutorial_integration_c_binding_types

Next tutorial: tutorial_integration_c_unaligned_advanced

type_mangling — complete type mangling reference