Error Handling Guide
This guide covers Sigil's error handling facilities in depth.
Basic Error Signaling
The simplest way to signal an error:
(error "Something went wrong")
(error "Invalid value" bad-value)This prints a message and returns #f. For structured error handling, use the exceptions library.
The Exceptions Library
(import (sigil error))Raising Exceptions
;; Raise any value as an exception
(raise "Something went wrong")
(raise (list 'my-error "details"))
;; Raise with a custom condition
(raise (my-error (message "Bad input")))Catching with guard
(guard (err
;; Pattern clauses
((string? err)
(display "String error: ")
(display err)
(newline)
#f)
((my-error? err)
(handle-my-error err))
(else
(display "Unknown error")
(newline)
#f))
;; Body that might raise
(risky-operation))The else clause catches anything not matched by previous clauses.
Re-raising
(guard (err
((recoverable? err)
(recover err))
(else
(log-error err)
(raise err))) ; Re-raise to outer handler
(operation))Custom Conditions
Define structured error types:
(define-record <validation-error>
(field)
(message)
(value))
(define (validate-age age)
(cond
((not (integer? age))
(raise (validation-error
(field 'age)
(message "Age must be an integer")
(value age))))
((< age 0)
(raise (validation-error
(field 'age)
(message "Age cannot be negative")
(value age))))
((> age 150)
(raise (validation-error
(field 'age)
(message "Age seems unrealistic")
(value age))))
(else age)))Handle with pattern matching:
(guard (err
((validation-error? err)
(display "Validation failed for ")
(display (validation-error-field err))
(display ": ")
(display (validation-error-message err))
(newline)
#f))
(validate-age input))Exception Handler Stack
Use with-exception-handler for low-level control:
(with-exception-handler
(lambda (condition)
;; Handle or re-raise
(if (can-handle? condition)
(recover condition)
(raise condition)))
(lambda ()
;; Protected code
(risky-operation)))Handlers are stacked — inner handlers run first:
(with-exception-handler outer-handler
(lambda ()
(with-exception-handler inner-handler
(lambda ()
(raise 'error))))) ; inner-handler called firstError Recovery Strategies
Default Values
(define (safe-divide a b)
(guard (err (else 0))
(/ a b)))
(safe-divide 10 2) ; => 5
(safe-divide 10 0) ; => 0Retry with Limits
(define (retry-operation op max-attempts)
(let loop ((attempts 0))
(guard (err
(else
(if (< attempts max-attempts)
(begin
(display "Retry ")
(display (+ attempts 1))
(newline)
(loop (+ attempts 1)))
(raise err)))) ; Give up
(op))))Result Types
Avoid exceptions for expected failures:
(define-record <result>
(ok?)
(value)
(error))
(define (ok value)
(result (ok? #t) (value value) (error #f)))
(define (err error)
(result (ok? #f) (value #f) (error error)))
(define (parse-int str)
(let ((n (string->number str)))
(if n
(ok n)
(err (str "Not a number: " str)))))
;; Usage
(let ((r (parse-int input)))
(if (result-ok? r)
(process (result-value r))
(display (result-error r))))Option Types
For "might not exist" scenarios:
(define (find-user id)
;; Returns user or #f
(assq id user-table))
(define (get-user-name id)
(let ((user (find-user id)))
(if user
(user-name user)
"Unknown")))Cleanup with Dynamic Wind
Ensure cleanup happens even if errors occur:
(dynamic-wind
(lambda () (display "Setup\n"))
(lambda ()
(display "Body\n")
(raise 'error))
(lambda () (display "Cleanup\n")))
;; Prints:
;; Setup
;; Body
;; Cleanup
;; Then raises 'errorBest Practices
1. Fail Fast
Validate inputs at function entry:
(define (process-items items)
(unless (list? items)
(raise (type-error (expected 'list) (got items))))
(unless (not (null? items))
(raise (value-error (message "Items cannot be empty"))))
;; ... process ...
)2. Be Specific
Good error messages include:
- What failed
- Why it failed
- What was expected
- What was received
;; Bad
(error "Invalid input")
;; Good
(error (str "Expected positive integer for 'count', got: " value))3. Document Errors
In docstrings, list what errors a function might raise:
;;; Read and parse a config file.
;;;
;;; Raises:
;;; file-not-found - if path doesn't exist
;;; parse-error - if config syntax is invalid
(define (read-config path)
...)4. Handle at Appropriate Level
Don't catch errors too early:
;; Bad: swallows all errors
(define (process x)
(guard (err (else #f))
(step1 x)
(step2 x)
(step3 x)))
;; Better: let caller decide
(define (process x)
(step1 x)
(step2 x)
(step3 x))
;; Caller handles appropriately
(guard (err
((step1-error? err) ...)
((step2-error? err) ...))
(process x))5. Log Before Re-raising
(guard (err
(else
(log-error "Operation failed" err)
(raise err)))
(critical-operation))Debugging
Stack Traces
When an unhandled error occurs, Sigil prints a stack trace showing:
- The error message
- Where it was raised
- The call stack
Inspecting Errors
(guard (err
(else
(display "Error type: ")
(display (type-of err))
(newline)
(write err)
(newline)
#f))
(risky-operation))Conditional Debugging
(define *debug* #t)
(define (debug-log msg)
(when *debug*
(display "[DEBUG] ")
(display msg)
(newline)))