Defer
The defer statement schedules a function call to execute when the current scope exits, enabling clean and reliable resource cleanup.
Basic Usage
Section titled “Basic Usage”Defer statements execute in LIFO (Last In, First Out) order when the enclosing scope exits:
import "std/io";
fn main() { defer io::Println("third"); defer io::Println("second"); defer io::Println("first");
io::Println("body");}bodyfirstsecondthirdScope-based Execution
Section titled “Scope-based Execution”Unlike some languages where defer runs only at function exit, Ferret’s defer is scope-based - it runs when any enclosing block exits:
Block Scopes
Section titled “Block Scopes”import "std/io";
fn example() { io::Println("start");
{ defer io::Println("block defer"); io::Println("inside block"); } // defer executes here
io::Println("after block");}startinside blockblock deferafter blockLoop Scopes
Section titled “Loop Scopes”Each loop iteration creates a new scope, so defers execute at the end of each iteration:
import "std/io";
fn loopExample() { for i in 0..3 { defer io::Println(i); io::Println("iteration"); }}iteration0iteration1iteration2If Statement Scopes
Section titled “If Statement Scopes”import "std/io";
fn conditional(x: i32) { if x > 0 { defer io::Println("positive cleanup"); io::Println("positive branch"); } else { defer io::Println("non-positive cleanup"); io::Println("non-positive branch"); }}Defer with Error Handling
Section titled “Defer with Error Handling”Defer can be combined with error handling using catch clauses. When a deferred function returns a Result type, you can handle errors that occur during cleanup:
import "std/io";
fn mayFail() -> str ! str { return "cleanup error"!;}
fn main() { io::Println("Start");
defer mayFail() catch err { // This executes ONLY if mayFail() returns an error io::Println("Caught error in defer:", err); };
io::Println("End");}StartEndCaught error in defer: cleanup errorHow Defer with Catch Works
Section titled “How Defer with Catch Works”The catch block in a defer statement is diagnostic-only and executes conditionally:
- If the deferred call succeeds (returns Ok): The catch block is skipped
- If the deferred call fails (returns Err): The catch block executes with the error
import "std/io";
fn successfulCleanup() -> str ! str { io::Println(" Cleanup succeeded"); return "ok";}
fn failingCleanup() -> str ! str { io::Println(" Cleanup failed"); return "error"!;}
fn main() { io::Println("Test 1: Success case"); defer successfulCleanup() catch err { io::Println(" Catch:", err); };
io::Println("Test 2: Error case"); defer failingCleanup() catch err { io::Println(" Catch:", err); };}Test 1: Success caseTest 2: Error case Cleanup failed Catch: error Cleanup succeededImportant: Catch is Diagnostic-Only
Section titled “Important: Catch is Diagnostic-Only”The catch block in defer is for error logging and diagnostics only. Unlike regular catch blocks, it cannot alter control flow or return values:
import "std/io";
fn example() -> i32 { defer mayFail() catch err { // You can log the error io::Println("Defer error:", err);
// But return statements here do NOT affect the function's return value // return 42; // This would be ignored };
return 10; // This is what the function returns}This design ensures that:
- Cleanup errors can be logged and observed
- The original function’s control flow and return value are preserved
- Defer remains focused on cleanup, not altering program behavior
Control Flow with Defer
Section titled “Control Flow with Defer”Defer statements respect control flow:
import "std/io";
for i in 0..10 { if i == 5 { defer io::Println("breaking at", i); break; // defer runs before break exits loop } defer io::Println("iteration", i);}All defers in scopes being exited will execute before the break completes.
Continue
Section titled “Continue”import "std/io";
for i in 0..5 { defer io::Println("end of iteration", i);
if i % 2 == 0 { continue; // defer runs before continue }
io::Println("odd:", i);}Return
Section titled “Return”import "std/io";
fn cleanup() { defer io::Println("final cleanup");
if someCondition { defer io::Println("early cleanup"); return; // Both defers execute: early first, then final }
io::Println("normal path");}All defers from all scopes execute before the function returns, in reverse order of declaration.
Common Patterns
Section titled “Common Patterns”Resource Cleanup
Section titled “Resource Cleanup”fn processResource() { let file := openFile("data.txt"); defer file.close();
// Use file... // file.close() automatically called on scope exit}Resource Cleanup with Error Handling
Section titled “Resource Cleanup with Error Handling”import "std/io";
fn closeResource() -> str ! str { io::Println(" Closing resource..."); return "close failed"!; // Simulating a close error}
fn processData() { io::Println("Opening resource"); defer closeResource() catch err { io::Println(" Warning: Resource cleanup failed:", err); };
io::Println("Processing data");}
fn main() { processData(); io::Println("Done");}Opening resourceProcessing data Closing resource... Warning: Resource cleanup failed: close failedDoneTransaction Management
Section titled “Transaction Management”import "std/io";
fn commitTransaction() -> str ! str { io::Println(" Committing..."); // Simulate transaction commit return "commit succeeded";}
fn rollbackTransaction() { io::Println(" Rolling back transaction");}
fn transaction() -> bool { io::Println("Begin transaction");
defer commitTransaction() catch err { io::Println(" Commit failed:", err); rollbackTransaction(); };
io::Println("Performing operations..."); return true;}
fn main() { let result := transaction(); io::Println("Transaction completed:", result);}Begin transactionPerforming operations... Committing...Transaction completed: trueLock Management
Section titled “Lock Management”fn criticalSection() { mutex.lock(); defer mutex.unlock();
// Critical section code... // unlock automatically called}Execution Order Summary
Section titled “Execution Order Summary”- Within a scope: Defers execute in LIFO order (last declared, first executed)
- Across scopes: Inner scope defers execute before outer scope defers
- On return: All defers from all scopes execute before function returns
- On break/continue: Defers in exiting scopes execute before control transfer
- With catch blocks: Catch executes only if deferred call returns an error
- Catch is diagnostic: Catch blocks cannot alter function return values or control flow
Best Practices
Section titled “Best Practices”- Use defer for resource cleanup to ensure it always happens
- Keep defer statements close to resource acquisition for clarity
- Remember that defer executes at scope exit, not statement end
- Use defer with catch for logging cleanup errors, not for error recovery
- Catch blocks in defer are diagnostic-only and cannot change return values
- Avoid complex logic in defer - keep it simple and focused on cleanup