Skip to content

Defer Statements

Defer statements in Harneet provide a powerful mechanism for ensuring cleanup code runs when a function exits, regardless of how it exits (normal return, early return, or error). This is particularly useful for resource management, cleanup operations, and ensuring consistent program behavior.

Basic Syntax

Basic Defer Syntax
defer functionCall()
defer fmt.Println("This runs when function exits")

How Defer Works

  • Execution Order: Deferred calls execute in LIFO (Last In, First Out) order
  • Timing: Deferred calls execute just before the function returns
  • Argument Capture: Arguments are evaluated and captured when the defer statement is encountered
  • Guaranteed Execution: Deferred calls execute regardless of how the function exits

Basic Example

Basic Example
package main
import fmt

function basicDeferExample() {
    fmt.Println("Function start")

    defer fmt.Println("This will execute last")
    defer fmt.Println("This will execute second to last")

    fmt.Println("Function middle")
    fmt.Println("Function end")
}

function main() {
    basicDeferExample()
}

Output:

1
2
3
4
5
Function start
Function middle
Function end
This will execute second to last
This will execute last

Resource Management

Defer is commonly used for cleanup and resource management:

Resource Management
package main
import fmt
function resourceManagementExample() {
    fmt.Println("Opening resource...")

    defer fmt.Println("Closing resource (cleanup)")
    defer fmt.Println("Releasing memory")
    defer fmt.Println("Logging operation complete")

    fmt.Println("Using resource...")
    fmt.Println("Processing data...")

    // Resource is automatically cleaned up via defer
}

Defer with Multiple Return Paths

Defer ensures cleanup happens regardless of which return path is taken:

Multiple Return Paths
package main
import fmt
function multipleReturnPaths(condition bool) {
    defer fmt.Println("Cleanup always happens")

    if condition {
        fmt.Println("Taking early return path")
        return  // Defer still executes
    }

    fmt.Println("Taking normal execution path")
    // Defer executes here too
}

Argument Capture

Arguments to deferred functions are captured when the defer statement is encountered:

Captured Variables
package main
import fmt
function deferWithCapturedVariables() {
    var message = "Initial message"

    // This defer captures the current value of message
    defer fmt.Printf("Deferred message: %s\n", message)

    // Change the message after defer
    message = "Modified message"
    fmt.Printf("Current message: %s\n", message)

    // The deferred call will still use "Initial message"
}

Defer with Anonymous Functions

You can defer user-defined functions for more complex cleanup logic:

User-Defined Functions
package main
import fmt
function cleanupFunction() {
    fmt.Println("Performing cleanup operations")
}

function deferWithUserFunctions() {
    var counter = 0

    defer cleanupFunction()
    defer fmt.Printf("Final counter value: %d\n", counter)

    counter = 5
    fmt.Printf("Counter set to: %d\n", counter)
}

Defer in Loops

Each iteration of a loop creates a separate deferred call:

Defer in Loops
package main
import fmt
function deferInLoop() {
    fmt.Println("Starting loop with defer")

    for var i = 0; i < 3; i = i + 1 {
        defer fmt.Printf("Deferred from iteration %d\n", i)
        fmt.Printf("Loop iteration %d\n", i)
    }

    fmt.Println("Loop completed")
}

Output:

1
2
3
4
5
6
7
8
Starting loop with defer
Loop iteration 0
Loop iteration 1
Loop iteration 2
Loop completed
Deferred from iteration 2
Deferred from iteration 1
Deferred from iteration 0

Error Handling with Defer

Defer statements execute even when errors occur:

Defer with Errors
package main
import fmt
function functionWithError() {
    defer fmt.Println("Cleanup happens even with errors")

    fmt.Println("About to cause an error...")
    var result = 10 / 0  // This will cause an error

    fmt.Printf("Result: %d\n", result)  // This won't execute
    // But defer still executes
}

Comprehensive Error Handling

Harneet's defer implementation includes advanced error handling:

  • Error Aggregation: Multiple defer errors are collected and reported together
  • Stack Trace Preservation: Error stack traces are maintained for debugging
  • Panic Recovery: Panics in deferred calls are caught and converted to errors
  • Resource Limits: Protection against excessive defer stack usage
Error Function
package main
import fmt
function errorFunction() {
    var x = 1 / 0  // Error in defer
}

function multipleDeferredErrors() {
    defer fmt.Println("This defer works")
    defer errorFunction()  // This will fail
    defer fmt.Println("This defer also works")

    fmt.Println("Main function completes")
    // All working defers execute, errors are aggregated
}

Best Practices

1. Use Defer for Cleanup

Defer for Cleanup
1
2
3
4
5
6
function processFile(filename string) {
    // Open file
    defer closeFile()  // Ensure file is closed

    // Process file...
}

2. Defer Immediately After Resource Acquisition

Defer After Acquisition
1
2
3
4
5
6
function acquireResource() {
    var resource = openResource()
    defer resource.close()  // Defer immediately after acquisition

    // Use resource...
}

3. Be Careful with Loop Variables

Loop Variables
// Correct: Capture loop variable
package main
import fmt
for var i = 0; i < 5; i = i + 1 {
    defer fmt.Printf("Iteration %d\n", i)  // Captures current value of i
}

// Alternative: Use a helper function if you need final value
var finalI = 0
function printFinalI() {
    fmt.Printf("Final i: %d\n", finalI)
}

for var i = 0; i < 5; i = i + 1 {
    finalI = i
    defer printFinalI()  // Uses final value of i
}

4. Order Matters

Defer Order
1
2
3
4
5
6
7
8
9
function setupAndCleanup() {
    setupA()
    defer cleanupA()  // Executes second

    setupB()
    defer cleanupB()  // Executes first

    // Use resources...
}

Advanced Features

Stack Overflow Protection

Harneet includes protection against excessive defer usage: - Maximum defer stack size limits - Recursive defer call detection - Emergency cleanup procedures

Error Aggregation

When multiple deferred calls fail, Harneet aggregates the errors: - Detailed error reporting for each failed defer - Stack trace preservation - Continued execution of remaining defers

Performance Considerations

  • Defer has minimal runtime overhead
  • Arguments are captured efficiently
  • Stack management is optimized for typical usage patterns

Common Patterns

File Operations

File Operations
1
2
3
4
5
6
function processFile(filename string) {
    var file = openFile(filename)
    defer file.close()

    // Process file safely
}

Lock Management

Lock Management
1
2
3
4
5
6
function criticalSection() {
    mutex.lock()
    defer mutex.unlock()

    // Critical section code
}

Logging and Monitoring

Logging and Monitoring
1
2
3
4
5
6
7
8
package main
import fmt
function trackedOperation(name string) {
    fmt.Printf("Starting %s\n", name)
    defer fmt.Printf("Completed %s\n", name)

    // Operation code
}

Error Recovery

Error Recovery
package main
import fmt
function cleanupOperation() {
    fmt.Println("Operation cleanup")
}

function safeOperation() {
    defer cleanupOperation()

    // Potentially risky operation
}

Summary

Defer statements provide a robust mechanism for:

  • Resource Management: Automatic cleanup of resources
  • Error Safety: Guaranteed execution even with errors
  • Code Organization: Clean separation of setup and teardown
  • Reliability: Consistent behavior across all exit paths

The defer mechanism in Harneet is designed to be both powerful and safe, with comprehensive error handling and protection against common pitfalls.