# How to Use the onCleanup Mechanism in Nelson for Reliable Resource Management

> Learn how to use the onCleanup mechanism in nelson-lang/nelson to automatically release resources like file handles even during errors. Ensure reliable resource management.

- Repository: [The Nelson Programming Language/nelson](https://github.com/nelson-lang/nelson)
- Tags: how-to-guide
- Published: 2026-03-08

---

**Use the `onCleanup` function in nelson-lang/nelson to register a callback that executes automatically when a function exits, ensuring resources like file handles are released even during errors.**

The `onCleanup` mechanism provides deterministic resource cleanup for the Nelson numerical programming environment. Based on a handle class architecture similar to MATLAB's `onCleanup`, this feature guarantees that registered functions execute when the parent macro function terminates, regardless of whether the exit is normal or caused by an error.

## What Is the onCleanup Mechanism?

The `onCleanup` mechanism implements a **resource acquisition is initialization (RAII)** pattern for Nelson scripts. When you create an `onCleanup` object inside a function, the interpreter stores a reference to your cleanup callback in the current macro function's internal task stack.

According to the nelson-lang/nelson source code, the mechanism relies on three core components:

- **`OnCleanupObjectHandle`**: A C++ handle class that stores the user-provided function handle and manages execution state.
- **`MacroFunctionDef::cleanupTasks`**: A vector that maintains the stack of pending cleanup objects for the current function scope.
- **`onCleanup` builtin**: The gateway function that validates inputs and registers handles with the cleanup stack.

## How onCleanup Works Under the Hood

### The Builtin Registration Process

In [`modules/interpreter/builtin/cpp/onCleanupBuiltin.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/builtin/cpp/onCleanupBuiltin.cpp), the builtin performs strict validation before creating the cleanup handle:

```cpp
ArrayOfVector
Nelson::InterpreterGateway::onCleanupBuiltin(Evaluator* eval, int nLhs,
                                            const ArrayOfVector& argIn)
{
    nargincheck(argIn, 1, 1);
    nargoutcheck(nLhs, 0, 1);

    const ArrayOf& functionArray = argIn[0];
    if (!functionArray.isFunctionHandle()) {
        Error(ERROR_WRONG_ARGUMENT_1_TYPE_FUNCTION_HANDLE_EXPECTED);
    }

    auto* onCleanupObj = new OnCleanupObjectHandle(functionArray);
    ArrayOf handleArray = ArrayOf::handleConstructor(onCleanupObj);
    
    // Registration happens only inside macro functions
    Context* ctx = eval->getContext();
    FunctionDef* funcDef = nullptr;
    const std::string& currentName = ctx->getCurrentScope()->getName();
    if (ctx->lookupFunction(currentName, funcDef) &&
        funcDef->type() == NLS_MACRO_FUNCTION) {
        static_cast<MacroFunctionDef*>(funcDef)->addCleanupFunction(handleArray);
    }
    return ArrayOfVector{handleArray};
}

```

The builtin ensures that **only function handles** are accepted and that registration occurs **only when inside a macro function**.

### The Cleanup Stack Architecture

The `MacroFunctionDef` class in [`modules/interpreter/src/include/MacroFunctionDef.hpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/src/include/MacroFunctionDef.hpp) maintains the cleanup task stack:

```cpp
void addCleanupFunction(ArrayOf& task) {
    cleanupTasks.push_back(task);
}
ArrayOfVector cleanupTasks;

```

When a macro function terminates, the interpreter calls `MacroFunctionDef::onCleanup()` (defined in [`modules/interpreter/src/cpp/MacroFunctionDef.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/src/cpp/MacroFunctionDef.cpp)), which iterates through the stack:

```cpp
void MacroFunctionDef::onCleanup(Evaluator* eval)
{
    for (auto task : cleanupTasks) {
        if (task.isHandle() && task.getHandleClassName() == NLS_HANDLE_ONCLEANUP_CATEGORY_STR) {
            OnCleanupObjectHandle* obj =
                static_cast<OnCleanupObjectHandle*>(task.getContentAsHandleScalar());
            if (obj && obj->isScoped()) {
                obj->cleanup(eval);
            }
        }
    }
    cleanupTasks.clear();
}

```

### Handle Execution and Cancellation

The `OnCleanupObjectHandle` class in [`modules/interpreter/src/cpp/OnCleanupObjectHandle.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/src/cpp/OnCleanupObjectHandle.cpp) executes the stored callback:

```cpp
void OnCleanupObjectHandle::cleanup(Evaluator* eval)
{
    if (isCanceled) {
        return;
    }
    function_handle fh = task.getContentAsFunctionHandle();
    FunctionDef* funcDef = nullptr;
    if (fh.anonymousHandle != nullptr) {
        funcDef = static_cast<FunctionDef*>(fh.anonymousHandle);
    }
    if (funcDef) {
        funcDef->evaluateFunction(eval, ArrayOfVector(), 0);
    }
    cancel();
}

```

After execution, the `cancel()` method sets `isCanceled = true`, preventing **double execution** if the handle is somehow triggered again.

## Practical Usage Examples

### Basic File Resource Management

The most common use case ensures files are closed even if errors occur:

```matlab
function processData(filename)
    % Open resource
    fid = fopen(filename, 'w');
    
    % Register cleanup - executes when function exits
    onCleanupObj = onCleanup(@() fclose(fid));
    
    % Your processing logic here
    fprintf(fid, 'Processing data...\n');
    
    % If an error occurs here, fclose still runs
    
    % Normal exit also triggers cleanup
end

```

When `processData` returns, the interpreter automatically calls `fclose(fid)`, preventing resource leaks.

### Forcing Early Cleanup

You can trigger cleanup immediately by clearing the handle:

```matlab
function demoExplicit()
    cleanup = onCleanup(@() disp('Cleanup executed now'));
    disp('Doing work...');
    
    clear cleanup;  % Forces immediate execution
    
    disp('Work finished');
end

```

Output:

```

Doing work...
Cleanup executed now
Work finished

```

### Canceling Cleanup Tasks

To prevent a registered cleanup from running, call the `cancel` method:

```matlab
function conditionalCleanup()
    fid = fopen('temp.txt', 'w');
    cleanupObj = onCleanup(@() fclose(fid));
    
    % Some logic that might keep the file open
    success = processFile(fid);
    
    if success
        % Keep file open, cancel auto-close
        cleanupObj.cancel();
        return;  % fclose will NOT run
    end
    
    % If not successful, function exit triggers fclose
end

```

## Key Source Files and Architecture

Understanding the implementation helps debug complex scenarios. The nelson-lang/nelson repository organizes the cleanup mechanism across these files:

| File Path | Component |
|-----------|-----------|
| [`modules/interpreter/builtin/cpp/onCleanupBuiltin.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/builtin/cpp/onCleanupBuiltin.cpp) | Builtin gateway that validates function handles and registers cleanup tasks |
| [`modules/interpreter/src/include/OnCleanupObjectHandle.hpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/src/include/OnCleanupObjectHandle.hpp) | Handle class declaration storing the callback |
| [`modules/interpreter/src/cpp/OnCleanupObjectHandle.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/src/cpp/OnCleanupObjectHandle.cpp) | Implementation of `cleanup()` and `cancel()` methods |
| [`modules/interpreter/src/include/MacroFunctionDef.hpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/src/include/MacroFunctionDef.hpp) | Declaration of `cleanupTasks` vector and `addCleanupFunction()` |
| [`modules/interpreter/src/cpp/MacroFunctionDef.cpp`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/src/cpp/MacroFunctionDef.cpp) | Execution loop that invokes cleanup tasks at function exit |
| `modules/interpreter/tests/onCleanup/demo_onCleanup.m` | Demonstration script showing usage patterns |
| [`modules/interpreter/help/en_US/xml/onCleanup.xml`](https://github.com/nelson-lang/nelson/blob/main/modules/interpreter/help/en_US/xml/onCleanup.xml) | Official documentation source |

## Summary

- **Create** an `onCleanup` object by passing a function handle to `onCleanup()` inside a macro function.
- The interpreter **stores** the handle in `MacroFunctionDef::cleanupTasks`, a stack managed by the current function scope.
- At function exit, `MacroFunctionDef::onCleanup` iterates the stack and invokes each handle's `cleanup` method, which executes your callback and marks the task as cancelled to prevent double execution.
- Use `clear obj` to **force immediate cleanup** or `obj.cancel()` to **suppress** the cleanup action entirely.
- The mechanism requires execution inside a macro function; calling `onCleanup` at the command line or in scripts not wrapped in functions will not register the task.

## Frequently Asked Questions

### What happens if I call onCleanup outside of a function?

If you call `onCleanup` at the command prompt or in a script that is not inside a function definition, the builtin cannot register the cleanup task. According to [`onCleanupBuiltin.cpp`](https://github.com/nelson-lang/nelson/blob/main/onCleanupBuiltin.cpp), registration only occurs when `ctx->lookupFunction` finds a `NLS_MACRO_FUNCTION`. The object is created but never added to `cleanupTasks`, so your callback will never execute.

### Can I register multiple onCleanup objects in one function?

Yes. The `cleanupTasks` member in `MacroFunctionDef` is an `ArrayOfVector` that acts as a stack. Each call to `onCleanup` pushes a new handle onto this vector. When the function exits, `MacroFunctionDef::onCleanup` iterates through all registered tasks in the order they were added, ensuring every resource is properly released.

### How do I prevent a cleanup task from running?

Call the `cancel()` method on the `onCleanup` object. In [`OnCleanupObjectHandle.cpp`](https://github.com/nelson-lang/nelson/blob/main/OnCleanupObjectHandle.cpp), this method sets the internal `isCanceled` flag to `true`. When the function scope ends, the `cleanup` method checks this flag and returns immediately without executing the callback. This is useful when you need to keep a resource open beyond the function scope.

### Is onCleanup similar to Python's with statement or C++ RAII?

Conceptually yes, though the implementation differs. Like Python's `with` or C++ destructors, `onCleanup` guarantees execution of cleanup code when a scope exits. However, Nelson's implementation uses an explicit handle object registered with the interpreter's `MacroFunctionDef` cleanup stack rather than implicit destructor calls. The handle pattern gives you explicit control via `cancel()` and `clear()`, making it more flexible than strict RAII while providing the same safety guarantees.