Error handling

cx has no exceptions. Instead, the current idioms for reporting failures are nullable return values and status codes, checked by the caller:

int? parseDigit(char c) {
    if c >= '0' && c <= '9' {
        return int(c) - int('0');
    }
    return null;
}

void main() {
    if parseDigit('7') {
        println("it's a digit");
    }

    var result = parseDigit('x');
    if result == null {
        println("not a digit");
    }
}

See Nullable types for how the compiler checks that nullable values aren't used without a null check.

Result type

When a failure carries information about what went wrong, return a Result<T, E> holding either the success value (Ok) or the error (Err):

Result<int, string> parseDigit(char c) {
    if c >= '0' && c <= '9' {
        return Ok(int(c) - int('0'));
    }
    return Err("not a digit");
}

void main() {
    switch (parseDigit('7')) {
        case Ok value:
            println(value); // prints 7
        case Err error:
            println(error);
    }
}

Use == Ok and == Err to test which case a result holds. unwrap returns the success value, or aborts with the error:

Result<int, string> parseDigit(char c) {
    if c >= '0' && c <= '9' {
        return Ok(int(c) - int('0'));
    }
    return Err("not a digit");
}

void main() {
    var digit = parseDigit('7');
    if digit == Ok {
        println(unwrap(digit)); // prints 7
    }

    var notDigit = parseDigit('x');
    if notDigit == Err {
        println("not a digit");
    }
}

Assertions

Use assert to check conditions that must hold if the program is correct. If the condition is false, the program prints the location and aborts.

int divide(int a, int b) {
    assert(b != 0);
    return a / b;
}

void main() {
    println(divide(7, 2)); // prints 3
}

assert takes an optional second argument, a string literal used as the failure message. It is printed along with the location of the assertion.

void main() {
    var x = 2 + 2;
    assert(x == 4, "math is broken");
    println("math works"); // prints math works
}

assert(false) marks unreachable code: the compiler knows execution cannot continue past it, so no return is needed after it. To abort unconditionally with a message, call abort:

void fail(string message) {
    abort("fatal error: ", message);
}

void main() {
    var ok = true;
    if !ok {
        fail("something went wrong");
    }
    println("continuing");
}

Unimplemented code

Use todo to mark code that isn't written yet. It aborts with "Not implemented", or with the given reason, so unfinished paths fail loudly instead of silently doing the wrong thing. Since todo never returns, the compiler accepts it anywhere a value is expected.

int parsePort(string service) {
    if service == "http" {
        return 80;
    }
    todo("only http is supported for now");
}

void main() {
    println(parsePort("http")); // prints 80
}