Repository navigation
Error
Warp has two ways for something to go wrong: an error value that a function hands back, and a raised error that
stops the program unless a try catches it. Both carry the same Error value, so the same failed test works on both.
error "text" makes an Error value. A function returns it like any other value, and the caller asks failed:
no try, no catch.
f(x) := if x < 0 then error "neg" else x
r = f(-1)
if r failed then 1 else 2 # 1
f(3) failed is false. r is failed is the same test.
An error value nothing uses warns: if x < 0 { error "neg" } drops it, fail "neg" raises it.
The Error knows its text:
f(x) := if x < 0 then error "neg" else x
r = f(-1)
r.message # "neg"
An Error passes through what you do with it. Arithmetic on it gives the same Error back, so a caller further up can still test it:
f(x) := if x < 0 then error "neg" else x
g(x) := f(x) + 1
r = g(-1)
r failed # yes
or gives a fallback, ! insists on the value and stops the program with the error:
f(x) := if x < 0 then error "neg" else x
r = f(-1)
r or 0 # 0
An Error that is still the result at the end of the program is printed, and warp exits with status 1.
Some errors are not handed back, they are raised: raise "text" (or throw), 1/0, an index out of range,
assert x > 0 else "text". A raised error stops the program at once, also inside an assignment:
print "a"
r = 10/0 # stops here: Error("divide by zero at 2:1")
print "b" # never runs
It names where it happened, line and column, and points at the code. The position is the operation that failed, also when it runs inside a function called elsewhere:
f(x) := 10 / x; f(0)
» Error("divide by zero at 1:9")
1 | f(x) := 10 / x; f(0)
| ^^
A bare try catches a raised error and keeps it as its value, so failed tests it as above:
r = try 10/0
if r failed then r.message else 2 # "divide by zero"
try catches the error however deep inside the call it was raised.
try X else Y gives Y instead; Y is a value or an action:
try 1/0 else 0 # 0
try { … } catch { … } is the same with blocks, and catch e names the Error:
try { raise "boom" } catch e { e.message } # "boom"
Python's try: … except: … works too.
finally runs whatever happens; the value of try, or its Error, stays the result:
n = 0
r = try 10/2 finally n = 7
r + n # 12
A handler attached to a function from outside: every call of the function that fails gives the handler's value, as if
its body were try body else handler.
divide(x) := 10/x
on error of divide { 0 }
divide(0) + divide(5) # 2
on error of f: 0 and on error of f do 0 are the same.
emit, on and the other signal words are a separate mechanism: emit alarm runs the on alarm handlers and
the program goes on. raise and throw are always errors: an on handler never receives them, only try does.
A function of a cargo crate or a WebAssembly component that returns a Rust Err (a WIT result error) raises it
in warp, with the crate's message:
use cargo semver
semver.Version.parse("x")
» Error("… unexpected character 'x' while parsing major version number")
try catches it like any other error: try semver.Version.parse("x") else 0 is 0.
effects of f shows when f can fail: the effect Fail (effects). try removes it:
f(x) := 10/x
g(x) := try f(x) else 0
effects of f # Fail
effects of g # Allocation
The type of a function that can fail is T or error, a union like int or text; warp never shows a Result type.
T? is a T, ø or an Error:
def check(x) as int or error := if x < 0 then error("negative") else x
r = check(-1)
r failed # yes
int? n = ø
Results, variables and arguments carry an Error; a list does not. An Error written into a list stops the program with its own message:
e = try 10/0
[e 2] # divide by zero: a list item holds no error; handle it with try … else, or declare `or error`
[(try e else 0) 2] # [0 2]
- Only the bytes at a wit export would be a WIT
resultfor the other side; not done yet: inferringor error, and the WIT export. - An Error written by
xs#1 = e, into a field, a map or a tuple is still kept (cards writing-error, tuple-literal). - Where a caller can take it, an error could travel back as the Error value instead of being raised.
-
catchblocks inside a function, catching for the whole function (card catch-blocks):
fun do_dirty_stuff(){
eat ()
drink ()
catch (no food){}
catch (drunkenness){}
}
Decided, not built yet (card error-gaps): int? means an int, nothing (ø) or an Error, int or error stays the
precise form. An Error never lands in data: writing one into a field or a list element raises ("a field holds no error:
handle it or declare or error"). x is ø asks for missing only, x failed for failed only, if no x and x or 0
cover both. parse string as int? would return an int or an Error, int? i = try parse(s) would need no catch, and parse string as int may fail would mean the same as
int?. A function without ? that calls a failing one would itself be marked as failing (the Fail effect above),
so errors never color functions by hand. no result would be true for both missing and failed values
(optional).