Error Handling System
Harneet implements a comprehensive Go-style error handling system that emphasizes explicit error checking and prevents silent failures.
Core Principles
- No Silent Failures: All functions that can fail return an error value.
- Explicit Error Checking: Errors must be explicitly checked using the
if err != None pattern. - Error Type: Functions can return the
error type for simple error handling. - Multiple Return Values: Functions that return values use
(result, error) tuples. - 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 |
|---|
| 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 |
|---|
| 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
When an error occurs, Harneet displays detailed debugging information:
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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