Closures in Stoffel
This page explains how closures are implemented and used in Stoffel’s virtual machine, their structure, and their role in the programming model.Rationale
Closures in Stoffel enable higher-order functions and maintain lexical scoping semantics, providing a flexible programming model. The implementation uses a deep-copy approach for captured values to ensure predictable behavior across different execution contexts. This design choice allows developers to use functional programming patterns while maintaining clear boundaries between execution environments.Comprehensive Overview
A closure in Stoffel represents a function combined with its lexical environment (captured upvalues). Closures are first-class values that can be created, passed as arguments, returned from functions, and invoked. The VM implements closures through a combination of the coreClosure struct, specialized foreign functions for closure management, and integration with the activation records.
The closure implementation follows a deep-copy model, where captured values are duplicated rather than referenced. This approach ensures that each closure maintains its own independent state, avoiding unexpected interactions between different parts of the program.
Closure Structure
-
function_id: String: The name of the function wrapped by the closure.- Used to locate the function definition in the VM’s function registry
- Enables the VM to know which code to execute when the closure is called
-
upvalues: Vec<Upvalue>: The collection of captured variables from the closure’s defining environment.- Each upvalue contains a name and its associated value
- Enables access to variables from outer scopes when the closure executes
- Stored as deep copies to maintain independent state
Creating Closures
Closures are created using thecreate_closure foreign function, which takes a function name and a list of variable names to capture:
- The function validates that a function with the specified name exists
- For each variable name to capture, the VM:
- Searches the current scope chain for the named value
- Creates a new upvalue containing a deep copy of the found value
- Adds the upvalue to the closure’s environment
Calling Closures
Closures are invoked using thecall_closure foreign function, which takes a closure and its arguments:
- The function extracts the function ID and upvalues from the closure
- It validates that the function exists and has the correct arity
- It creates a new activation record with:
- The function’s name
- A fresh set of registers
- Copies of the closure’s upvalues
- The function arguments loaded into registers and locals
- The new activation record is pushed onto the VM’s stack, transferring control to the function
Accessing Upvalues
Within a closure, upvalues are accessed using specialized foreign functions:-
get_upvalue(name): Retrieves the current value of a captured variable.- Searches the current activation record’s upvalues for the named value
- Triggers an
UpvalueReadevent for debugging - Returns a copy of the upvalue’s current value
-
set_upvalue(name, value): Updates the value of a captured variable.- Searches the current activation record’s upvalues for the named value
- Updates the upvalue with the new value
- Triggers an
UpvalueWriteevent for debugging - Changes are local to the current closure instance