Lambdas/Closures module

This module provides as-ergonomic-as-possible Lambda/Closure facilities for C. Defining, binding, freeing, and otherwise working with lambdas (even when using a custom allocator) is made as simple as possible.

"Hello, World!" with Lambdas Example:

// we need to name our lambda type in order to take it as a function parameter
// (otherwise we'll get incompatible struct errors trying to bind different anonymous structs to
// the function parameter)
typedef Lambda(void, CnxString) LambdaVoidString;

// declare a lambda function named greet_place that returns `void` and takes in a `CnxString`
void LambdaFunction(greet_place, CnxString greeting) {
    // Retrieve the variable bindings from the lambda.
    // The binding types (`CnxString` here) and the types of the variables passed
    // to the generator macro (`lambda` or `lambda_with_allocator`)
    // __**MUST MATCH**__
    let binding = lambda_binding(CnxString);
    // get the first bound variable
    let place = binding._1;
    println("{}, {}!", greeting, place);
}

void greet_with_hello(LambdaVoidString lambda) {
    // call the lambda with "Hello" as the `greeting`
    lambda_call(lambda, cnx_string_from("Hello"));
    // Free the memory associated w/ the lambda
    // This will release the binding, and the lambda will no longer be valid
    lambda_free(lambda);
}

// Prints "Hello, World!"
void hello_world(void) {
    let world = cnx_string_from("World");
    // lambdas capture by value, so if you need to bind a pointer,
    // take the address of the variable.
    // The types of the arguments captured __**MUST MATCH**__ those used
    // in the binding retrieval in the LambdaFunction's definition
    // This binds `world` as the `CnxString` used as `place` in the
    // `LambdaFunction`'s definition
    let lambda = lambda(greet_place, world);
    // lambdas are generated as anonymous structs for brevity, so we have to cast them
    // to the named type to pass them to a function or store them
    greet_with_hello(lambda_cast(lambda, LambdaVoidString));
}

// Prints "Hello, C Programmers!"
void hello_c_programmers(void) {
    // multiple lambdas can be bound to a single `LambdaFunction` definition at the same time:
    // the bound data is separate from the `LambdaFunction`'s definition.
    // Because lambdas capture by value, they can bind either lvalues or rvalues
    let lambda = lambda(greet_place, cnx_string_from("C Programmers"));
    greet_with_hello(lambda_cast(lambda, LambdaVoidString));
}

Typedefs

using CnxLambdaCaptures = void*
Stores the captures/bindings of a lambda This is implicitly passed as the first parameter to a LambdaFunction, but as a user, will never need to be used or interacted with directly.

Functions

static void lambda_free(void* lambda)
Frees the given lambda, freeing associated memory and making it invalid for future use.

Defines

#define Lambda(ReturnType, ...)
The type of a complete lambda instance, binding a lambda function definition with a set of captured variables.
#define LambdaFunction(Name, ...)
A Lambda function definition. Can be bound to an instance of a lambda with lambda or lambda_with_allocator.
#define LambdaBinding(...)
A type storing a lambda's bound captures.
#define lambda_with_allocator(alloc, function_name, ...)
Binds the given function and captures as a lambda type, that can be called later.
#define lambda(function_name, ...)
Binds the given function and captures as a lambda type, that can be called later.
#define lambda_call(lambda, ...)
Calls the given lambda with the provided arguments as function parameters.
#define lambda_clone(lambda)
Returns a clone of the given lambda, ensuring that (by-value) captures stay valid for the lifetime of the clone.
#define ScopedLambda
Declaration tag to scope a lambda, ensuring it is freed when it goes out of scoped and associated resources are cleaned up.
#define lambda_binding(...)
Retrieves the bound captures from the enclosing lambda.
#define lambda_cast(lambda, Type)
Casts the given lambda to the given named type, so it can be passed to a function or stored.

Typedef documentation

typedef void* CnxLambdaCaptures

Stores the captures/bindings of a lambda This is implicitly passed as the first parameter to a LambdaFunction, but as a user, will never need to be used or interacted with directly.

Function documentation

static void lambda_free(void* lambda)

Frees the given lambda, freeing associated memory and making it invalid for future use.

Parameters
lambda - The lambda to free

Define documentation

#define Lambda(ReturnType, ...)

The type of a complete lambda instance, binding a lambda function definition with a set of captured variables.

Parameters
ReturnType - The return type of the lambda
... - The types of the lambda function arguments

#define LambdaFunction(Name, ...)

A Lambda function definition. Can be bound to an instance of a lambda with lambda or lambda_with_allocator.

Parameters
Name - The name of the function
... - The function argument list

#define LambdaBinding(...)

A type storing a lambda's bound captures.

Parameters
... - The captured variables (or types of the captures) of the lambda binding

#define lambda_with_allocator(alloc, function_name, ...)

Binds the given function and captures as a lambda type, that can be called later.

Parameters
alloc - The allocator with which to allocate memory to store the captured values in
function_name - The function to bind captures to as a lambda
... - The list of variables to capture and bind to the lambda
Returns a bound lambda

Binds the given function with the captures list (must capture at least one variable). Captures are captured by value and can be either lvalues or rvalues:

This version uses the provided memory allocator for memory allocation

#define lambda(function_name, ...)

Binds the given function and captures as a lambda type, that can be called later.

Parameters
function_name - The function to bind captures to as a lambda
... - The list of variables to capture and bind to the lambda
Returns a bound lambda

Binds the given function with the captures list (must capture at least one variable). Captures are captured by value and can be either lvalues or rvalues:

This version uses the default system allocator, DEFAULT_ALLOCATOR, for memory allocation

#define lambda_call(lambda, ...)

Calls the given lambda with the provided arguments as function parameters.

Parameters
lambda - The lambda to call
... - The arguments to pass to the lambda function
Returns The return value of the lambda function

#define lambda_clone(lambda)

Returns a clone of the given lambda, ensuring that (by-value) captures stay valid for the lifetime of the clone.

Parameters
lambda - The lambda to clone
Returns a clone of the lambda

#define ScopedLambda

Declaration tag to scope a lambda, ensuring it is freed when it goes out of scoped and associated resources are cleaned up.

#define lambda_binding(...)

Retrieves the bound captures from the enclosing lambda.

Returns The struct containing the bound captures from the enclosing lambda

#define lambda_cast(lambda, Type)

Casts the given lambda to the given named type, so it can be passed to a function or stored.

Parameters
lambda - The lambda to cast
Type - The named Lambda type to cast to. This must be a named typedef in order for the lambda to be able to be passed as a function parameter or stored
Returns lambda as Type