Skip to content
FutureBlitz

    Install

    Errors, 1 of 4

    Throw and Catch

    Declare what a function throws, throw an error, and handle it with Try, Catch, and Try?.

    What you’ll build

    A function that reads a port number from a setting, like "8080", and fails clearly when the text isn’t a usable port. Its callers handle the failure in three ways: catch it, pass it on, or turn it into a default.

    Throws and Throw

    A function that can fail lists what it throws after its result type, with Throws. Throw stops the code where it is with an error: an object of std.core’s Error Class, or of a Class extending it, made with a message. Unless a Try block around it in the same function catches it, the error leaves the function.

    port.fb
    Import std.core
    
    Function ParsePort(text: String): Int Throws Error
        Local port = Int.Parse(text)
        If port == Null Then Throw Error("'{text}' isn't a number")
        If port < 1 || port > 65535 Then Throw Error("{port} isn't between 1 and 65535")
        Return port
    End Function
    
    Try
        Print(ParsePort("8080"))
        Print(ParsePort("http"))
        Print("not reached")
    Catch e
        Print("bad port: {e.Message}")
    End Try
    Output
    8080
    bad port: 'http' isn't a number

    A Try block runs its body, and when something in it throws, the rest of the body is skipped and the Catch runs with the error. A call that can throw must be inside a Try block, or written with the Try or Try? prefix, which the next steps show. fb checks this when it compiles, so an error can’t be forgotten:

    port.fb
    Import std.core
    
    Function ParsePort(text: String): Int Throws Error
        Local port = Int.Parse(text)
        If port == Null Then Throw Error("'{text}' isn't a number")
        Return port
    End Function
    
    Print(ParsePort("8080"))
    $ fb run port.fb
       Compiling port.fb
    port.fb:9:7: error: 'ParsePort' can throw Error, so calling it needs Try, Try?, or a Try block
      |
    9 | Print(ParsePort("8080"))
      |       ^^^^^^^^^^^^^^^^^
    error: couldn't build port.fb because of 1 error

    Handling each value

    A Try block inside a loop handles each failure and goes on to the next value.

    port.fb
    Import std.core
    
    Function ParsePort(text: String): Int Throws Error
        Local port = Int.Parse(text)
        If port == Null Then Throw Error("'{text}' isn't a number")
        If port < 1 || port > 65535 Then Throw Error("{port} isn't between 1 and 65535")
        Return port
    End Function
    
    For text In ["8080", "http", "70000", "443"]
        Try
            Print("port {ParsePort(text)}")
        Catch e
            Print("skipped: {e.Message}")
        End Try
    End For
    Output
    port 8080
    skipped: 'http' isn't a number
    skipped: 70000 isn't between 1 and 65535
    port 443

    Passing an error on

    Try before a call lets the call’s errors go on to the caller, which must then declare them with Throws, or catch them itself. The function stops where the error happened, as it would for a Throw of its own.

    port.fb
    Import std.core
    
    Function ParsePort(text: String): Int Throws Error
        Local port = Int.Parse(text)
        If port == Null Then Throw Error("'{text}' isn't a number")
        Return port
    End Function
    
    Function Address(host: String, portText: String): String Throws Error
        Local port = Try ParsePort(portText)
        Return "{host}:{port}"
    End Function
    
    Try
        Print(Address("localhost", "8080"))
        Print(Address("localhost", "eighty"))
    Catch e
        Print("bad address: {e.Message}")
    End Try
    Output
    localhost:8080
    bad address: 'eighty' isn't a number

    Try?: a default instead

    Try? turns a call that throws into a value that can be Null: the call’s result, or Null when it throws, and the error is dropped. With ??, that gives a default.

    port.fb
    Import std.core
    
    Function ParsePort(text: String): Int Throws Error
        Local port = Int.Parse(text)
        If port == Null Then Throw Error("'{text}' isn't a number")
        Return port
    End Function
    
    Local port = Try? ParsePort("http") ?? 3000
    Print("listening on {port}")
    Output
    listening on 3000

    Use Try? when the error’s details don’t matter. When they do, catch it.

    Errors no one catches

    Top-level code can let an error out. The program then prints uncaught, the error’s Class and message, where it was thrown, and a stack trace, and exits with code 1.

    port.fb
    Import std.core
    
    Function ParsePort(text: String): Int Throws Error
        Local port = Int.Parse(text)
        If port == Null Then Throw Error("'{text}' isn't a number")
        Return port
    End Function
    
    Local port = Try ParsePort("http")
    Print(port)
    Output
    uncaught Error: 'http' isn't a number at port.fb:5:26
        at ParsePort (port.fb:5)
        at top-level code (port.fb:9)

    What you learned

    • Throws after a result type lists what a function can throw, and Throw throws it.
    • Every call that can throw needs a Try block, a Try prefix, or Try?.
    • Catch e handles an error, and e.Message says what went wrong.
    • Try call passes the error on to the caller, whose Throws must cover it.
    • Try? call gives Null on an error, and ?? turns that into a default.
    • An error that leaves top-level code prints where it was thrown, and exits with code 1.

    Report an issue with this page or with FutureBlitz on GitHub.