Error - pannous/warp GitHub Wiki

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.

Errors are values

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.

Raised errors

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)
    |         ^^

try: back to a value

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

on error of f: a handler from outside

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.

Errors are not signals

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.

Rust crates and components

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")

Not yet: try does not catch such an error, the program stops (card try-crate-errors).

Not yet (card error-gaps)

  • effects of f does not show that f can fail: f(x) := 10/x, raise and error "neg" all report Pure (probes/fail_effects.warp). Plan: a Fail effect, inferred like the others, which try, catch and on error of remove, and which ! Pure rejects (effects).
  • The type of a function that can fail is T or error (def parse(s) as int or error), a sound union like int or text; warp never shows a Result type. Only the bytes at a wit export are a WIT result for the other side; no warp user writes or reads it. Not done yet: inferring or error, and the WIT export.
  • Where a caller can take it, an error could travel back as the Error value instead of being raised.
  • A field of an Error is not an error yet: (try 10/0).count is 1 instead of the Error.
  • catch blocks 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).