Skip to content
FutureBlitz

    Install

    Errors, 4 of 4

    Panics and stack traces

    Stop the program on a bug with Assert and Panic, and read the stack trace that points at it.

    What you’ll build

    A function that averages groups of numbers and states its assumptions with Assert. When a caller breaks one, the program stops, and its stack trace leads from the failed check back to the call that caused it.

    Panics are for bugs

    A panic stops the program at once. It prints panic:, a message, and where it happened, then a stack trace, and exits with code 101. The language panics on its own for bugs it can see: an index out of range, a missing Map key, integer overflow in a debug build, and dividing an integer by zero.

    divide.fb
    Import std.core
    
    Function Share(total: Int, people: Int): Int => total / people
    
    Print(Share(100, 4))
    Print(Share(100, 0))
    Output
    25
    panic: division by zero at divide.fb:3:49
        at Share (divide.fb:3)
        at top-level code (divide.fb:6)

    A panic can’t be caught, and it doesn’t run Defer statements or Finally blocks: the program’s state is wrong, so nothing more of it runs.

    Assert

    Assert(condition, message) panics with the message when the condition is false. It states what a function counts on, so a broken assumption stops the program where it’s broken, instead of causing a wrong answer later. The message is only made when the check fails.

    average.fb
    Import std.core
    Import std.collections
    
    Function Average(values: Int[]): Int
        Assert(values.Count > 0, "Average needs at least one value")
        Local sum = 0
        For value In values
            sum += value
        End For
        Return sum / values.Count
    End Function
    
    Function Report(groups: Int[][]): String
        Local averages: String[] = []
        For group In groups
            averages.Add("{Average(group)}")
        End For
        Return averages.Join(", ")
    End Function
    
    Print(Report([[1, 2, 3], [10, 20]]))
    Print(Report([[4], []]))
    Output
    2, 15
    panic: Average needs at least one value at average.fb:5:5
        at Average (average.fb:5)
        at Report (average.fb:16)
        at top-level code (average.fb:22)

    The trace lists the calls the panic happened in, the innermost first, each with its file and line:

    • at Average (average.fb:5) is the failed Assert, in Average.
    • at Report (average.fb:16) is the line of Report that called Average.
    • at top-level code (average.fb:22) is the line of the program that called Report.

    The bug isn’t in Average, which checked its input as it should, but in the call at line 22, which passed an empty group. Every build has stack traces, debug and release alike, with no debug files to ship.

    Panic, Unreachable, and Todo

    Panic(message) panics outright, for a state the program should never be in. Unreachable() marks code that can’t be reached, and Todo(message) code still to be written. Each ends its path, so a function that returns a value can end with one.

    sign.fb
    Import std.core
    
    Function Sign(n: Int): String
        If n > 0 Then Return "positive"
        If n < 0 Then Return "negative"
        If n == 0 Then Return "zero"
        Unreachable()
    End Function
    
    Function Describe(value: Float): String
        Todo("describe Floats")
    End Function
    
    Print(Sign(-3))
    Print(Describe(1.5))
    Output
    negative
    panic: this isn't done yet: describe Floats at sign.fb:11:5
        at Describe (sign.fb:11)
        at top-level code (sign.fb:15)

    Errors or panics

    Throw an error for a failure that a correct program must expect, and that a caller can do something about: bad input, a missing file, a network that’s down. Panic, usually through Assert, for a mistake in the program itself, which only changing the code can fix. Here a person’s typing can be wrong, so ParseAge throws; but Percent is only ever called with a whole above 0 by correct code, so it asserts.

    ages.fb
    Import std.core
    
    Function ParseAge(text: String): Int Throws Error
        Local age = Int.Parse(text)
        If age == Null || age < 0 Then Throw Error("'{text}' isn't an age")
        Return age
    End Function
    
    Function Percent(part: Int, whole: Int): Int
        Assert(whole > 0, "Percent needs a whole above 0")
        Return part * 100 / whole
    End Function
    
    Local ages: Int[] = []
    For text In ["34", "old", "61"]
        Try
            ages.Add(ParseAge(text))
        Catch e
            Print("please try again: {e.Message}")
        End Try
    End For
    Local over40 = 0
    For age In ages
        If age > 40 Then over40++
    End For
    Print("{Percent(over40, ages.Count)}% are over 40")
    Print("{Percent(over40, 0)}%")
    Output
    please try again: 'old' isn't an age
    50% are over 40
    panic: Percent needs a whole above 0 at ages.fb:10:5
        at Percent (ages.fb:10)
        at top-level code (ages.fb:27)

    The bad input is reported and the program goes on; the bad call stops it.

    What you learned

    • A panic stops the program with a message, a place, and a stack trace, and exits with code 101.
    • Indexes out of range, missing Map keys, integer division by zero, and, in a debug build, integer overflow panic on their own.
    • Assert(condition, message) panics when an assumption is broken.
    • A stack trace lists the calls, innermost first, each with its file and line.
    • Panic, Unreachable(), and Todo() end a path on purpose.
    • Errors are for failures to expect; panics are for bugs.

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