Skip to main content

Lambdas

Lambdas in Zynx are non‑capturing, inline function expressions. They are primarily intended for callbacks, simple adapters, and other situations where defining a named top‑level function would be unnecessarily verbose.

Unlike closures in some other languages, Zynx lambdas do not capture local variables. They behave like lightweight, anonymous function pointers.

See also:


Syntax

Zynx supports two lambda forms:

  1. Explicit fn form:
fn (param: Type, ...) -> ReturnType { ... }
  1. Short contextual form:
(a, b) => a - b
(a, b) => { return a - b; }

Notes:

  • The return type is optional. If omitted, the lambda returns void.
  • In the explicit fn form, parameter types are explicit.
  • In the short form, parameter and return types are inferred from the expected function type at the call site.
  • The lambda body is a standard block and may contain local variables, return statements, and control flow constructs.

Short lambda constraints

The short form is only valid when the expected callback type is fully known from context.

  • Parameter types must be known.
  • Return type must be known.
  • If either is unknown, the short form is rejected.
  • Generic callback inference from short-lambda bodies is not performed implicitly. If needed, provide explicit generic arguments or use an explicit fn (...) -> ... lambda with full type information.

Example:

fn apply(x: i32, f: (i32) -> i32) -> i32 {
return f(x);
}

let y = apply(2, (v) => v + 1); // ok
let z = apply(2, (v) => { return v; }); // ok

Example:

let add_one = fn (x: i32) -> i32 {
return x + 1;
};

let y = add_one(2); // y == 3

Non‑capturing behavior

Lambdas do not capture local variables from surrounding scopes. They can only access:

  • Their own parameters.
  • Module‑level symbols (for example, imported modules, constants, or functions).

This property makes lambda lowering straightforward and avoids hidden heap allocations or lifetime complexities.

fn apply(x: i32, f: fn (i32) -> i32) -> i32 {
return f(x);
}

// Lambda expression passed directly as a callback
let result = apply(2, fn (x: i32) -> i32 {
return x + 1;
});

let result2 = apply(2, (x) => x + 1);

If you need to use local state, you must pass it explicitly as a parameter.

Passing state explicitly

// Lambda that adds an offset supplied by the caller
let add_with_offset = fn (x: i32, offset: i32) -> i32 {
return x + offset;
};

let v1 = add_with_offset(2, 1); // 3
let v2 = add_with_offset(5, 10); // 15

This pattern keeps lifetimes explicit and avoids implicit captures.


Function pointer types

Lambda expressions are typed as function pointers. You can:

  • Assign them to named function pointer types.
  • Pass them directly as arguments to functions expecting function pointers.
  • Store them in data structures.

Example with an explicit function pointer type:

type Mapper = fn (i32) -> i32;

fn map_one(x: i32, f: Mapper) -> i32 {
return f(x);
}

let square: Mapper = fn (x: i32) -> i32 {
return x * x;
};

let out = map_one(4, square); // 16

Usage patterns

Common patterns where lambdas are useful:

  • Callbacks for iteration or higher‑order utilities.
  • Small adapters to bridge API differences (for example, changing parameter order or adding logging).
  • Test helpers, where a full named function would add noise.

Example (simple callback):

fn for_each(xs: i32[], f: fn (i32) -> void) {
for v in xs {
f(v);
}
}

fn log_value(v: i32) {
os.write("value={v}\n");
}

fn demo() {
let xs = i32[3] { 1, 2, 3 };

// Use a named function
for_each(xs, log_value);

// Or an inline lambda
for_each(xs, fn (v: i32) {
os.write("inline={v}\n");
});
}

Limitations and design notes

  • Lambdas are non‑capturing by design. If you need closures that capture local variables, use a struct that holds the state and pass it explicitly, or add a normal named function that accepts the state as an additional parameter.
  • Lambda types participate in generics just like other function pointer types. See Generics for details on parameterizing over function pointers.

By keeping lambdas simple and non‑capturing, Zynx can represent them efficiently as plain function pointers, without introducing extra runtime machinery.