Robust Error Handling & Optionals

Ube has no runtime exceptions and no unwieldy try-catch blocks. Failure is represented explicitly in the type system through Result types (T ! E) and Optionals (T?).

1. Result Types (T ! E)

A function that can fail specifies both its success value type T and its error value type E separated by an exclamation mark: T ! E. Use the error(...) built-in to return an error variant:

fn parse_server_port(input: i32) -> i32 ! string {
    if input < 1024 {
        return error("port numbers below 1024 are reserved")
    }
    if input > 65535 {
        return error("port number exceeds maximum allowed (65535)")
    }
    return input // Success value returned directly
}

fn main() {
    let result = parse_server_port(8080)
    // Results must be handled via '?' or 'or_else'
}

2. The ? Propagation Operator

When calling a function that returns a Result within another function that also returns a Result, append ? to immediately return the error upon failure:

struct Config {
    port: i32
}

fn initialize_config(raw_port: i32) -> Config ! string {
    // If parse_server_port returns an error, execution returns early with that error
    let valid_port = parse_server_port(raw_port)?

    return Config {
        port: valid_port
    }
}

fn main() {
    let cfg = initialize_config(3000) or_else Config { port: 8080 }
    println(f"Server configured on port {cfg.port}")
}

3. Fallback Defaults with or_else

The or_else operator unwraps a Result, extracting the success value if valid, or falling back to a provided default expression if an error occurred:

fn main() {
    // Safe fallback value if parsing fails
    let fallback_port = parse_server_port(-1) or_else 80
    println(f"Resolved port: {fallback_port}") // 80

    let valid = parse_server_port(9000) or_else 80
    println(f"Resolved port: {valid}")         // 9000
}

4. Optionals (T?) and null

When a value may be absent without an explanatory error message, use an Optional type written as T?. Optionals can take any value of type T or literal null:

struct User {
    id: i32,
    username: string
}

fn query_user(id: i32) -> User? {
    if id == 42 {
        return User { id: 42, username: "John" }
    }
    return null
}

5. Pattern Unwrapping with if let

To safely unpack an optional value, use the if let construct. It binds the inner value inside the true branch:

fn main() {
    let lookup = query_user(42)

    if let user = lookup {
        println(f"Found user #{user.id}: {user.username}")
    } else {
        println("User was not found in database.")
    }
}

6. Stream & Channel Loops with while let

The while let statement combines looping and optional unwrapping. It continuously loops until an optional returns null or a channel closes:

fn drain_queue(ch: Chan[i32]) {
    // ch.recv() returns i32? (null when channel is empty and closed)
    while let item = ch.recv() {
        println(f"Processing queued item: {item}")
    }
    println("Queue drained cleanly.")
}