Skip to content

Error Handling System

Harneet implements a comprehensive Go-style error handling system that emphasizes explicit error checking and prevents silent failures.

Core Principles

  1. No Silent Failures: All functions that can fail return an error value.
  2. Explicit Error Checking: Errors must be explicitly checked using the if err != None pattern.
  3. Error Type: Functions can return the error type for simple error handling.
  4. Multiple Return Values: Functions that return values use (result, error) tuples.
  5. Error Propagation: Errors bubble up through the call stack unless handled.

Error Type

Functions can declare error as their return type for simple error handling:

Error Return Type
package main
import fmt

// Function with error return type
function validateAge(age int) error {
    if age < 0 {
        return Error("age cannot be negative")
    }
    if age > 150 {
        return Error("age is unrealistic")
    }
    return None  // Success - no error
}

// Usage
var err = validateAge(25)
if err != None {
    fmt.Println("Error:", err)
} else {
    fmt.Println("Age is valid")
}

Error Propagation

Error Propagation
package main

function validateInput(input string) error {
    if input == "" {
        return Error("input cannot be empty")
    }
    return None
}

function processInput(input string) error {
    var err = validateInput(input)
    if err != None {
        return err  // Propagate error up
    }

    fmt.Println("Processing:", input)
    return None
}

Multiple Return Values

Functions that return values use tuples:

Multiple Returns
package main

function divide(a int, b int) (int, error) {
    if b == 0 {
        return 0, Error("division by zero")
    }
    return a / b, None
}

var result, err = divide(10, 2)
if err != None {
    fmt.Println("Error:", err)
} else {
    fmt.Println("Result:", result)
}

Error Checking Pattern

Error Checking
1
2
3
4
5
6
7
8
package main
var value, err = riskyFunction()
if err != None {
    fmt.Println("Error occurred:", err)
    return err // Propagate error up
}
// Safe to use value here
fmt.Println("Success:", value)

Built-in Error Types

  • None: Represents no error (similar to Go's nil).
  • Error(message): Creates an error value with a descriptive message.
  • error type: Return type for functions that may fail.

Standard Library Error Handling

All standard library functions return errors:

Standard Library Errors
// String conversion with error handling
package main
var num, err = strings.ToInt("invalid")
if err != None {
    fmt.Println("Conversion failed:", err)
    // Handle error appropriately
}

// Math operations with error checking
var result, err = math.Pow(2, 64) // May overflow
if err != None {
    fmt.Println("Math error:", err)
}

Error Categories

  • TypeError: Type conversion or mismatch errors
  • ValueError: Invalid values (division by zero, invalid conversions)
  • RangeError: Values outside acceptable ranges
  • ArithmeticError: Mathematical operation errors

Runtime Assertions

The assert module provides runtime assertions for testing and debugging.

Assert Module
1
2
3
4
5
6
7
package main
import assert

// Development-time checks that panic on failure
assert.Assert(x > 0, "x must be positive")
assert.AssertEq(actual, expected, "values should match")
assert.AssertNe(result, None, "result should not be None")

Stack Trace System

Harneet provides comprehensive stack trace information for all runtime errors, making debugging fast and efficient.

Features

  • Precise Location: Exact file name, line number, and column position
  • Call Hierarchy: Complete function call stack showing execution path
  • Error Context: Clear error messages with full debugging information
  • Multi-level Traces: Nested function calls show complete call chain

Stack Trace Format

When an error occurs, Harneet displays detailed debugging information:

1
2
3
4
ERROR: division by zero
Stack trace:
  at <program> (examples/myfile.ha:5:22)
  at math.Max (examples/myfile.ha:5:33)

Stack Trace Components

Each stack frame includes: - Function Name: <program> for top-level code, module.function for library calls - File Location: Complete file path relative to execution directory
- Position: Exact line:column where error occurred or call was made

Error Types with Stack Traces

Arithmetic Errors

Arithmetic Errors
1
2
3
4
5
6
package main
var result = 10 / 0  // Division by zero
// Output:
// ERROR: division by zero
// Stack trace:
//   at <program> (myfile.ha:1:15)

Identifier Errors

Identifier Errors
1
2
3
4
5
6
package main
var x = undefinedVariable  // Variable not found
// Output:
// ERROR: identifier not found: undefinedVariable
// Stack trace:
//   at <program> (myfile.ha:1:8)

Function Call Errors

Function Call Errors
1
2
3
4
5
6
7
8
package main
import math
var result = math.Max(42, 10 / 0)  // Error in nested call
// Output:
// ERROR: division by zero
// Stack trace:
//   at <program> (myfile.ha:2:22)
//   at math.Max (myfile.ha:2:33)

Type Conversion Errors

Type Conversion Errors
1
2
3
4
5
6
7
package main
import strings
var num, err = strings.ToInt("not_a_number")
if err != None {
    // Error is handled gracefully with descriptive message
    fmt.Println("Error:", err)
}

Parser Errors

Parser Errors
1
2
3
4
5
6
7
8
9
package main
var x = 42 +   // Incomplete expression (syntax error)
// Output:
// Woops! Sincere apologies, but we ran into some errors while running the program!
// Stack trace:
//  parser errors:
//    no prefix parse function for DIVIDE found
//    no prefix parse function for ERROR_TYPE found
//    no prefix parse function for ILLEGAL found

Benefits

  • Immediate Location: Know exactly where errors occur
  • Call Context: Understand how execution reached the error
  • File Information: No guessing which file contains the problem
  • Debugging Speed: Quick navigation to problem areas