Here is the rule that completes the model:
An error value cannot be compared and cannot decide a branch. It is not falsy. It is not truthy. It is unanswerable, and Crow treats it that way.
Consider if bad gt 5 where bad is a failed int[...]. Exceptions
would have crashed earlier; truthy/falsy languages would silently pick a
branch — misclassifying corrupt input as a small number. Crow does
neither:
1 2 3
bad = int['x']
if bad gt 5
log['big']expected output — from the book, verified by crow test --book
error: cannot branch on an error value — cannot convert 'x' to intAt top level, the program halts with the original reason. Inside a function, something better happens: the function returns the error to its caller — the decision that couldn't be made becomes the caller's failure to inspect:
1 2 3 4 5 6 7 8 9 10 11 12
fn size_of[s]
n = int[s]
if n gt 100
return 'big'
return 'small'
r1 = size_of['150']
log[r1]
r2 = size_of['xyz']
log[r2.ok]
log[r2.reason]expected output — from the book, verified by crow test --book
big
false
cannot convert 'xyz' to intsize_of contains no error handling at all — it's written purely for the
happy path. Feed it garbage and it returns the garbage's story, at the
exact moment the garbage mattered. The failure detonates at the decision,
not at the origin, and never silently.
A loop is a decision too. An error has no items to walk, so a for over a
list that failed is refused the same way: for i in range[len[r]] inside a
function hands r's error back when r failed, and at the top level it
halts with cannot loop over an error value.
An error could also slip past in a variable. A variable whose first value could not fail never holds an error: a later value that fails is refused right where it is assigned, the same way a decision refuses one.
1 2 3 4 5 6 7 8 9 10
fn add_up[items]
acc = 0.0
for s in items
acc = acc + float[s]
return acc
log[add_up[['1.5', '2']]]
bad = add_up[['1.5', 'two']]
log[bad.ok]
log[bad.reason]expected output — from the book, verified by crow test --book
3.5
false
cannot convert 'two' to floatacc began as a plain number, so the failed item never lands in it:
add_up hands the error back at that line. At the top level the program
halts there with cannot assign an error value to 'acc'. When you want to
keep a failure and look at it, give it a name of its own whose first value
can fail: r = float[s], then if not r.ok.
A few failures have no error value to become. xs[i] reads an element and
a / b of two ints makes an int, and neither an element nor an int can say
it failed. So an index outside its array, a key the map lacks, an int
divided by zero, or an int that no longer fits in 64 bits stops the program
on the line it happened on:
1 2 3 4 5
fn pick[xs, i]
return xs[i]
log[pick[[10, 20, 30], -1]]
log[pick[[10, 20, 30], 5]]expected output — from the book, verified by crow test --book
30
runtime error: line 2: index 5 is out of range for an array of 3 itemsA negative index counts from the end, so -1 is the last item. The stop
names line 2, inside pick, where the index was read. The words are the
same in crow run -i and crow run, and in a handler the click stops with
nothing it did landing. When an index might be outside, check len first,
as you would guard any decision.