Defer Statements
The defer
keyword provides automatic cleanup and resource management in Utah functions. Deferred statements execute automatically when a function exits, regardless of how it exits (normal return, early return, or error).
Basic Syntax
Simple Defer Statement
function processFile(filename: string): void {
let file = fs.openFile(filename);
defer file.close(); // Always executes when function exits
// Process the file...
if (someCondition) {
return; // file.close() still executes
}
// More processing...
} // file.close() executes here too
Generated Bash:
processFile() {
local filename="$1"
local _utah_defer_commands_processFile=()
# Set up cleanup trap
trap '_utah_cleanup_processFile' RETURN ERR EXIT
local file
file=$(fs_openFile "${filename}")
# Register defer command
_utah_defer_commands_processFile+=("file_close \"${file}\"")
# Function body...
if [ "${someCondition}" = "true" ]; then
return
fi
}
_utah_cleanup_processFile() {
local i
for ((i=${#_utah_defer_commands_processFile[@]}-1; i>=0; i--)); do
eval "${_utah_defer_commands_processFile[i]}"
done
}
Execution Order
LIFO (Last In, First Out)
Deferred statements execute in reverse order of their declaration:
function setupResources(): void {
defer console.log("Cleanup step 1");
defer console.log("Cleanup step 2");
defer console.log("Cleanup step 3");
console.log("Function body");
}
Output:
Function body
Cleanup step 3
Cleanup step 2
Cleanup step 1
Multiple Defer Statements
function complexCleanup(): void {
let connection = db.connect();
defer connection.close();
let tempDir = "$(mktemp -d)";
defer "$(rm -rf ${tempDir})";
// Process file directly without opening a file handle
let logContent: string = fs.readFile("process.log");
defer fs.appendFile("cleanup.log", "Cleanup completed\n");
// Work with resources...
if (errorCondition) {
return; // All three cleanup actions execute in reverse order
}
}
Common Patterns
Resource Management
function downloadAndProcess(url: string, outputFile: string): void {
// Create temporary directory
let tempDir = "$(mktemp -d)";
defer "$(rm -rf ${tempDir})";
// Download file
let downloadPath = "${tempDir}/download.tmp";
web.download(url, downloadPath);
defer fs.delete(downloadPath);
// Process and write to output file
let processedData: string = processDownloadedData(downloadPath);
defer fs.writeFile(outputFile, processedData);
// Process and write...
}
Lock Management
function criticalSection(): void {
let lockFile = "/tmp/process.lock";
// Acquire lock
fs.createFile(lockFile);
defer fs.delete(lockFile);
// Critical work that must be protected...
}
Cleanup Notifications
function longRunningTask(): void {
console.log("Starting long task...");
defer console.log("Task completed!");
// Set up progress tracking
defer console.log("Cleaning up progress tracking");
// Actual work...
for (let i = 0; i < 100; i++) {
// Process item i
}
}
Error Handling
Defer with Try/Catch
function robustProcessing(): void {
let resource = acquireResource();
defer resource.release(); // Always executes, even if exception occurs
try {
// Risky operation
processData(resource);
} catch (error) {
console.log("Error occurred: ${error}");
// defer still executes after catch block
}
}
Conditional Cleanup
function conditionalSetup(useBackup: boolean): void {
let primaryResource = setupPrimary();
defer primaryResource.cleanup();
if (useBackup) {
let backupResource = setupBackup();
defer backupResource.cleanup(); // Only executes if backup was created
}
// Work with resources...
}
Advanced Usage
Variable Capture
Variables referenced in defer statements are captured by value at the time the defer is declared:
function variableCapture(): void {
let message = "Initial";
defer console.log("Deferred: ${message}"); // Captures "Initial"
message = "Modified";
console.log("Current: ${message}"); // Prints "Modified"
}
Output:
Current: Modified
Deferred: Initial
Function Calls in Defer
function complexCleanup(): void {
let config = loadConfig();
defer saveConfig(config); // Function call with argument
let server = startServer(config.port);
defer server.stop(); // Method call
// Server operations...
}
Best Practices
1. Pair Resource Acquisition with Defer
// Good: Immediate defer after acquisition
function goodPattern(): void {
let file = fs.openFile("data.txt");
defer file.close(); // Paired immediately
// Use file...
}
// Avoid: Defer far from acquisition
function avoidPattern(): void {
let file = fs.openFile("data.txt");
// Lots of code...
defer file.close(); // Easy to miss or forget
}
2. Use Defer for All Cleanup
function comprehensiveCleanup(): void {
// File system cleanup
let tempFile = fs.createTempFile();
defer fs.delete(tempFile);
// Network cleanup
let connection = net.connect("api.example.com");
defer connection.close();
// Process cleanup
let process = system.startBackground("worker");
defer process.kill();
}
3. Avoid Complex Logic in Defer
// Good: Simple cleanup calls
defer file.close();
defer connection.disconnect();
// Avoid: Complex logic in defer
defer {
if (someCondition) {
// Complex cleanup logic
}
} // Not supported - defer only accepts simple statements
Limitations
1. Function Scope Only
Defer statements can only be used inside functions:
// Error: defer outside function
defer console.log("Invalid"); // Compilation error
function validUsage(): void {
defer console.log("Valid"); // OK
}
2. Simple Statements Only
Defer only accepts simple statements, not complex control structures:
function limitations(): void {
// Valid defer statements
defer file.close();
defer console.log("Done");
defer cleanup();
// Invalid defer statements
defer { // Error: blocks not supported
if (condition) {
doCleanup();
}
}
defer for (let i = 0; i < 10; i++) { // Error: loops not supported
cleanup(i);
}
}
3. No Defer Modification
Once declared, defer statements cannot be cancelled or modified:
function noModification(): void {
defer cleanup();
// Cannot cancel or modify the defer
// The cleanup() call will always execute
}
Implementation Details
Utah implements defer using bash trap handlers that execute when functions exit. The implementation ensures:
- Automatic execution on all exit paths (return, error, normal completion)
- LIFO ordering through array-based command storage
- Variable isolation to prevent interference between functions
- Error resilience so defer execution continues even if individual commands fail
The generated bash code uses function-specific arrays to store defer commands and trap handlers to ensure cleanup occurs reliably.