BRAIDGROUP
RESEARCH & DEV
18. Documentation

Error Handling

Current Approach

Braid v1 does not have try/except or exception handling mechanisms in the parser or runtime. Instead, error handling is done through return value checking, nil returns, and pattern matching. This follows a philosophy similar to Go's explicit error checks.

Nil-Returning Functions

When an operation can fail, the function returns nil to signal failure. The caller checks the result before proceeding.

fn divide_safe(a: float, b: float) {
    if b == 0.0 {
        return nil;
    }
    return a / b;
}

fn lookup_user(id: int) {
    if id < 0 {
        return nil;
    }
    return User { id: id, name: "user_" + id };
}

Checking for Nil

After calling a function that may return nil, use an if check to handle the failure case.

fn read_config(path: string) {
    let file = open_file(path);
    if file == nil {
        print("failed to open config");
        return;
    }
    let data = read_file(file);
    print("config: " + data);
}

fn main() {
    let result = divide_safe(10.0, 0.0);
    if result == nil {
        print("division by zero!");
    } else {
        print("result: " + result);
    }
}

Pattern Matching on Nil

Use match to handle both success and failure branches cleanly. Thenil pattern matches the null value, and the wildcard _matches success.

fn find_user(id: int) {
    if id != 1 {
        return nil;
    }
    return User { name: "Alice", age: 30 };
}

fn main() {
    let user = find_user(1);
    match user {
        nil => print("user not found"),
        _ => print("found: " + user.name),
    }
}

Guard Pattern with Early Returns

A common pattern is to check for invalid inputs or failed operations early in a function and return immediately. This reduces nesting and keeps the happy path clear.

fn process_file(path: string) {
    if path == "" {
        print("empty path");
        return;
    }
    let file = open_file(path);
    if file == nil {
        print("could not open: " + path);
        return;
    }
    print("processing " + path);
    close_file(file);
}

Boolean Flag Returns

For operations that succeed or fail without a value, return a bool flag.

fn validate_email(email: string) -&gt; bool {
    if email == "" {
        return false;
    }
    if email.find("@") == -1 {
        return false;
    }
    return true;
}

fn main() {
    let valid = validate_email("alice@example.com");
    if !valid {
        print("invalid email");
    } else {
        print("email ok");
    }
}

Status Code Pattern

For operations with multiple failure modes, use an integer status code or enum approach.

enum Status {
    OK;
    NotFound;
    PermissionDenied;
    InternalError;
}

fn delete_record(id: int) -> Status {
    if id < 0 {
        return Status.NotFound;
    }
    if id == 0 {
        return Status.PermissionDenied;
    }
    return Status.OK;
}

fn main() {
    let status = delete_record(0);
    match status {
        Status.OK => print("deleted"),
        Status.NotFound => print("not found"),
        Status.PermissionDenied => print("permission denied"),
        _ => print("unknown error"),
    }
}

Future Plans

Future versions of Braid may introduce more structured error handling, including:

  • try/catch blocks for exception-style handling
  • Result<T, E> generic type for typed error returns
  • ? operator for propagating errors (like Rust or Zig)
  • Stack traces for debugging error origins