Heroes / Examples / 03-fallible

examples/gallery/03-fallible.hero

Return failures as values and handle them explicitly.

A function returning T? gives either a T value or an error. fail creates the error, ? returns it to the caller, and match handles success and failure. Heroes has no exceptions: the return type says whether a call can fail.

Run it

$ heroes run examples/gallery/03-fallible.hero

These files introduce the language one feature at a time. This page shows the program and not a result, because no test compares their output byte for byte yet.

The program

## Failure is a value
#
# There is no `null` and there are no exceptions. A `T?` is a `T` or an
# error, `fail` makes one, `?` hands it to the caller, and `match` is the
# general way to look.

# Parses a decimal digit. Fails on anything else.
function digit_of(c: u8) -> i64?
    if c < '0' || c > '9'
        return fail(code: "not_a_digit", msg: "expected 0-9")
    return ok(to_i64(c - '0').must())

# Reads a whole non-negative number. Empty input is an error, not a zero.
function number_of(text: str) -> i64?
    if text.len() == 0
        return fail(code: "empty", msg: "there is nothing to read")
    total: i64 @= 0

    for i in range(from: 0, to: text.len())
        total @ total * 10 + digit_of(text[i])?

    return ok(total)

# The `?` above is the whole point: three lines, no error handling in sight,
# and the first bad character stops everything.
function average_of(parts: [str]) -> i64?
    sum: i64 @= 0

    for p in parts
        sum @ sum + number_of(p)?

    if parts.len() == 0
        return fail(code: "empty", msg: "no parts to average")
    return ok(sum / parts.len())

# Four ways to read a fallible value, in the order you reach for them.
function report(text: str) -> str
    match number_of(text)
        .ok v  => return "read " + v.to_str()
        .err e => return "failed: " + e.code + " (" + e.msg + ")"

function first_or_zero(parts: [str]) -> i64
    return number_of(parts[0]).default(0)

test "a good number and a bad one"
    assert number_of("120").must() == 120
    assert number_of("12x").is_err()
    assert report("7") == "read 7"
    assert first_or_zero(["oops"]) == 0

function main()
    print(report("42"))
    print(report("4b2"))
    print(average_of(["10", "20", "30"]).default(-1))
examples/gallery/03-fallible.hero, 54 lines

fingerprint sha256 24f91adb4042 · 1 file · index.json

Next: 04-loops. All the examples. ← Previous: 02-tokens

Run one yourself: the four commands. Learn the language, or read the full specification in one file.