Sign in

    #Error Package Documentation

    This package documents MoonBit's error handling system. Error::to_string and the Show and ToJson implementations for Error are provided by builtin; the Debug implementation is provided by debug. No error import is needed to use them.

    #Basic Error Usage

    MoonBit uses a structured error system with raise and try constructs:

    ///|
    test "basic error handling" {
    fn divide(a : Int, b : Int) -> Int raise {
    if b == 0 {
    raise Failure::Failure("Division by zero")
    } else {
    a / b
    }
    }

    // Successful operation
    let result1 = try! divide(10, 2)
    inspect(result1, content="5")

    // Handle error with try/catch/noraise
    try divide(10, 0) catch {
    e => debug_inspect(e, content="Failure(\"Division by zero\")")
    } noraise {
    _ => fail("expected error")
    }
    }

    #Custom Error Types

    Define custom error types using suberror:

    ///|
    suberror ValidationError {
    ValidationError(String)
    }

    ///|
    suberror NetworkError {
    NetworkError(String)
    }

    ///|
    test "custom errors" {
    fn validate_email(email : String) -> String raise ValidationError {
    if email.length() > 5 { // Simple validation without string methods
    email
    } else {
    raise ValidationError("Invalid email format")
    }
    }

    fn fetch_data(url : String) -> String raise NetworkError {
    if url.length() > 10 { // Simple validation
    "data"
    } else {
    raise NetworkError("Invalid URL")
    }
    }

    // Test validation error
    try validate_email("short") catch {
    _ => ()
    } noraise {
    _ => fail("expected validation error")
    }

    // Test network error
    try fetch_data("short") catch {
    _ => ()
    } noraise {
    _ => fail("expected network error")
    }
    }

    #Error Display and JSON Conversion

    The builtin package provides Show and ToJson implementations:

    ///|
    // Older compilers expand this derive through deprecated String::to_json.
    #warnings("-deprecated")
    priv suberror MyError {
    MyError(Int)
    } derive(ToJson)

    ///|
    test "error display and json" {
    let error : Error = MyError(42)

    // Error can be displayed as string
    let error_string = error.to_string()
    inspect(error_string.length() > 0, content="true")

    // Error can be converted to JSON
    let error_json = Json(error)
    @debug.debug_inspect(
    error_json,
    content="Array([String(\"MyError\"), Number(42)])",
    )
    }

    #Error Propagation and Handling

    Handle errors at different levels of your application:

    ///|
    suberror ParseError {
    ParseError(String)
    }

    ///|
    suberror FileError {
    FileError(String)
    }

    ///|
    test "error propagation" {
    fn parse_number(s : String) -> Int raise ParseError {
    if s == "42" {
    42
    } else {
    raise ParseError("Invalid number: " + s)
    }
    }

    fn read_and_parse(content : String) -> Int raise {
    parse_number(content) catch {
    ParseError(msg) => raise FileError("Parse failed: " + msg)
    }
    }

    // Success case
    let result1 = try! read_and_parse("42")
    inspect(result1, content="42")

    // Error propagation
    try read_and_parse("invalid") catch {
    _ => ()
    } noraise {
    _ => fail("expected error propagation")
    }
    }

    #Resource Management with Finally

    Perform cleanup in a catch handler before re-raising (or use defer for unconditional cleanup):

    ///|
    suberror ResourceError {
    ResourceError(String)
    }

    ///|
    test "resource management" {
    fn risky_operation() -> String raise ResourceError {
    raise ResourceError("Something went wrong")
    }

    // Simple resource management pattern
    fn use_resource() -> String raise {
    // Acquire resource (simulated)
    risky_operation() catch {
    ResourceError(_) =>
    // Cleanup happens here
    raise Failure::Failure("Operation failed after cleanup")
    }
    }

    try use_resource() catch {
    _ => ()
    } noraise {
    _ => fail("expected resource error")
    }
    }

    #Error Composition

    Combine multiple error-producing operations:

    ///|
    suberror ConfigError {
    ConfigError(String)
    }

    ///|
    suberror DatabaseError {
    DatabaseError(String)
    }

    ///|
    test "error composition" {
    fn load_config() -> String raise ConfigError {
    if true {
    "config_data"
    } else {
    raise ConfigError("Config not found")
    }
    }

    fn connect_database(config : String) -> String raise DatabaseError {
    if config == "config_data" {
    "connected"
    } else {
    raise DatabaseError("Invalid config")
    }
    }

    fn initialize_app() -> String raise {
    let config = load_config() catch {
    ConfigError(msg) => raise Failure::Failure("Config error: " + msg)
    }
    let db = connect_database(config) catch {
    DatabaseError(msg) => raise Failure::Failure("Database error: " + msg)
    }
    "App initialized with " + db
    }

    let app_result = try! initialize_app()
    inspect(app_result, content="App initialized with connected")
    }

    #Best Practices

    1. Use specific error types: Create custom suberror types for different error categories
    2. Provide meaningful messages: Include context and actionable information in error messages
    3. Handle errors at appropriate levels: Don't catch errors too early; let them propagate to where they can be properly handled
    4. Use try! for operations that should not fail: This will panic if an error occurs, making failures visible during development
    5. Use try? for recoverable errors: This returns a Result type that can be pattern matched
    6. Implement proper cleanup: Use the protect pattern or similar constructs for resource management

    #Performance Notes

    • Error handling in MoonBit is zero-cost when no errors occur
    • Error propagation is efficient and doesn't require heap allocation for the error path
    • Custom error types with derive(ToJson) automatically generate efficient JSON serialization