Error Handling
Catching errors with try/raise, understanding error types, null propagation, and writing defensive Rayfall code.
Rayfall provides structured error handling through try and raise, three distinct null representations for missing data, and consistent error propagation rules. This guide covers every pattern you need for robust Rayfall programs.
1. try/raise Basics
The try special form evaluates an expression and, if it produces an error, calls a handler function with the error value. The raise function creates an error explicitly.
Basic Syntax
(try expr handler)
- expr — the expression to evaluate
- handler — a function that receives the error value if
exprfails
Both arguments are passed unevaluated (try is a special form). The handler is only evaluated and called if expr produces an error.
Catching Division by Zero
(try
(/ 10 0)
(fn [err] (println "caught:" err)))
; caught: 0
When an error occurs without an explicit raise, the handler receives a default value of 0.
Raising Custom Errors
(try
(raise "invalid input")
(fn [err] (println "error:" err)))
; error: "invalid input"
With raise, the handler receives the exact value you raised — a string, number, or any Rayfall object.
Returning a Default Value
(try
(/ 10 0)
(fn [e] -1))
; => -1
The result of try is either the successful result of expr or the return value of the handler. This makes try an expression you can embed anywhere.
Raising Structured Errors
;; raise any value, not just strings
(try
(raise 42)
(fn [e] (+ e 8)))
; => 50
You can raise any Rayfall value. The handler receives it directly, so you can raise numbers, vectors, or even tables if you want structured error information.
When No Error Occurs
(try
(+ 2 3)
(fn [e] 0))
; => 5
If the expression succeeds, the handler is never called and the result passes through unchanged.
2. Error Types
Rayfall produces several categories of runtime errors. Understanding these helps you write better handlers.
Type Errors
Occur when an operation receives the wrong argument type:
(+ 1 "hello")
; error: type
Arithmetic, comparison, and most builtins check argument types and produce type errors for mismatches.
Arity Errors
Occur when a function receives the wrong number of arguments:
(+ 1)
; error: arity — + expects 2 arguments
Domain Errors
Occur when arguments have valid types but invalid values:
(/ 10 0)
; error: domain — division by zero
(take 100 [1 2 3])
; takes as many as available (no error for over-take)
User-Raised Errors
Any error you create with raise:
(raise "custom error message")
; error: domain
Internally, raise stores your value and triggers a domain error. The try handler receives the stored value.
Access Errors (Restricted Mode)
In restricted evaluation mode (used for IPC), certain functions are blocked:
; on a restricted server connection:
(.sys.exec "rm -rf /")
; error: access — restricted
How Errors Display in the REPL
Unhandled errors print with a category prefix:
rf> (/ 1 0)
error: domain
rf> (+ 1 "x")
error: type
Errors are terminal — an unhandled error stops evaluation of the current expression and returns to the REPL prompt.
3. Null Propagation
Missing data is a first-class concept in Rayforce. There are three distinct null forms, each with different behavior.
The Three Null Forms
- RAY_NULL_OBJ — the void return value (from
println,show) - Typed nulls —
0Ni(int),0Nf(float),0Nd(date) — missing values in data - Null bitmaps — per-element null flags on vectors
Typed Null Literals
Typed nulls represent missing data within a specific type. They look like values but carry a null flag:
;; Typed null literals
0Ni ; null integer
0Nf ; null float
0Nd ; null date
Null Propagation Through Arithmetic
Typed nulls propagate through arithmetic — any operation involving a typed null produces a typed null:
(+ 1 0Ni)
; => 0Nl (promoted to i64 null because 1 is i64)
(* 3.14 0Nf)
; => 0Nf
(+ 0Ni 0Ni)
; => 0Ni
This is the standard SQL/database behavior: null in, null out. It prevents silent corruption of calculations with missing data.
RAY_NULL_OBJ (Void Return)
Some operations like println and show return a void null. This is different from typed nulls — using it in arithmetic produces a type error, not propagation:
(set x (println "hello"))
; hello
(+ x 1)
; error: type
Nulls in Vectors
Vectors track null elements via a compact bitmap. Null elements propagate through vectorized operations:
(set v [1 0Ni 3 4 0Ni])
(+ v 10)
; => [11 0Ni 13 14 0Ni]
All Nulls Are Falsy
Every null form — void null, typed nulls, and null vector elements — evaluates to false in conditional contexts:
(if 0Ni "truthy" "falsy")
; => "falsy"
(if 0Nf "truthy" "falsy")
; => "falsy"
Testing for Null with nil?
The nil? function detects all null forms:
(nil? 0Ni)
; => 1
(nil? 42)
; => 0
(nil? (println "x"))
; x
; => 1
nil? is one of the few functions that safely handles the void null without producing a type error.
Null Propagation in Arithmetic
Arithmetic with typed nulls propagates the null through:
(+ [1 0Ni 3] [10 20 30])
; => [11 0Ni 33] — null element stays null
(sum [1 0Ni 3])
; => 4 — aggregates skip nulls
4. Defensive Patterns
Practical patterns for writing Rayfall code that handles edge cases gracefully.
Check for Null Before Operating
(set val 0Ni) ; could be a result from a lookup
(if (nil? val)
0
(* val 2))
; returns 0 for null, otherwise doubles the value
Default Value Pattern
;; define a "default" helper
(set with-default (fn [x d]
(if (nil? x) d x)))
(with-default 0Ni -1)
; => -1
(with-default 42 -1)
; => 42
Safe Division
(set safe-div (fn [a b]
(if (== b 0)
0Nf
(/ a b))))
(safe-div 10 3)
; => 3.333...
(safe-div 10 0)
; => 0Nf
Guard with try for File I/O
;; safely load a CSV, return empty table on failure
(set data
(try
(.csv.read "data.csv")
(fn [e]
(println "failed to load:" e)
(table [x] (list [])))))
; if file missing: prints error, returns empty table
Validate Table Columns Before Querying
;; check that required columns exist
(set validate-cols (fn [tbl required]
(set have (key tbl))
(map (fn [c]
(if (nil? (find have c))
(raise (format "missing column: {}" c))))
required)))
;; use it before a query
(try
(do
(validate-cols trades ['sym 'price 'qty])
(select {from: trades by: {sym: sym} cols: {total: (sum qty)}}))
(fn [e] (println "validation failed:" e)))
5. Error Recovery in Pipelines
When building data pipelines, errors in one stage should not always halt the entire computation.
Errors in select/update
If a computed column expression fails inside select, the entire select produces an error. Wrap the select in try to recover:
(set trades (table [sym price qty]
(list [AAPL GOOG MSFT]
[150.5 2800.0 310.0]
[100 50 75])))
;; safe query wrapper
(set safe-query (fn [q]
(try q
(fn [e]
(println "query failed:" e)
0Ni))))
Processing Rows with Error Tolerance
;; process each row, collecting errors separately
(set results
(map (fn [row]
(try
(/ (get row revenue) (get row shares))
(fn [e] 0Nf)))
rows))
; failed rows get 0Nf, successful rows get the result
Chaining try for Multi-Step Pipelines
;; load -> transform -> aggregate, with recovery at each step
(set result
(try
(do
(set raw (.csv.read "input.csv"))
(set clean (select {from: raw where: (> price 0)}))
(select {from: clean by: {sym: sym} cols: {p: (avg price)}}))
(fn [e]
(println "pipeline failed:" e)
0Ni)))
Nested try for Granular Recovery
;; inner try catches load failure, outer catches query failure
(try
(do
(set data
(try
(.csv.read "primary.csv")
(fn [e]
(println "primary failed, trying backup")
(.csv.read "backup.csv"))))
(select {from: data by: {sym: sym} cols: {total: (sum qty)}}))
(fn [e] (println "all attempts failed:" e)))
6. Quick Reference
| Pattern | Syntax | Behavior |
|---|---|---|
| Catch errors | (try expr handler) |
Calls handler with error value on failure |
| Raise error | (raise value) |
Triggers error, value passed to nearest try handler |
| Test for null | (nil? x) |
Returns 1 for any null form, 0 otherwise |
| Typed null int | 0Ni |
Propagates through arithmetic |
| Typed null float | 0Nf |
Propagates through arithmetic |
| Typed null date | 0Nd |
Propagates through date operations |
| Null in if | (if 0Ni ...) |
All nulls are falsy |
| Default value | (if (nil? x) d x) |
Substitute default for missing data |
Next Steps
- Control Flow — if/do/fn and other control structures
- Data Types — Full type reference including null representations
- Memory & Monitoring — Understanding memory budgets and GC
- Select Queries — Building queries with select/update