Mojo Assertions

An assertion is a statement that declares something must be true at a specific point in your program. If it is not true, the program halts immediately with a clear error message. Assertions act as sanity checks that catch bugs early — during development and testing — before they silently corrupt results in production.

The Security Guard Analogy

  Your function = a VIP room
  Assertion = the security guard at the door

  Without guard:
    Anyone enters → chaos inside

  With assertion:
    Guard checks: "Is x positive?"
    If yes → entry allowed
    If no  → STOP, loud alarm, clear message

  Assertions stop broken assumptions before they do damage.

Basic Assertion

from testing import assert_true, assert_false, assert_equal

fn main() raises:
    var x = 10

    assert_true(x > 0, "x must be positive")    # passes silently
    assert_true(x > 20, "x must be positive")   # FAILS — program halts
    print("This line never runs")

When an assertion fails, Mojo raises an Error containing the message you provided. The program stops at that line, giving you the exact location and reason for the failure.

assert_true and assert_false

from testing import assert_true, assert_false

fn validate_score(score: Int) raises:
    assert_true(score >= 0, "Score cannot be negative")
    assert_true(score <= 100, "Score cannot exceed 100")
    assert_false(score == 0 and score == 100,
                 "Score cannot be both 0 and 100")

fn main() raises:
    validate_score(85)    # all assertions pass
    validate_score(-5)    # first assertion fails:
                          # "Score cannot be negative"

assert_equal and assert_not_equal

from testing import assert_equal, assert_not_equal

fn add(a: Int, b: Int) -> Int:
    return a + b

fn main() raises:
    assert_equal(add(2, 3), 5, "2 + 3 must equal 5")
    assert_equal(add(0, 0), 0, "0 + 0 must equal 0")
    assert_not_equal(add(1, 1), 3, "1 + 1 must not equal 3")

    print("All assertions passed!")

Assertions as Documentation

Assertions describe the rules your function assumes. They serve as living documentation that runs and checks itself every time the code executes.

fn compute_discount(price: Float64, discount_pct: Float64) -> Float64 raises:
    # These assertions document the rules for valid input
    assert_true(price >= 0.0, "Price must be non-negative")
    assert_true(discount_pct >= 0.0, "Discount cannot be negative")
    assert_true(discount_pct <= 100.0, "Discount cannot exceed 100%")

    return price * (1.0 - discount_pct / 100.0)

fn main() raises:
    print(compute_discount(200.0, 25.0))   # 150.0
    print(compute_discount(100.0, 110.0))  # FAILS: Discount cannot exceed 100%
Assertion flow:
  compute_discount(100.0, 110.0)
       │
       ├── price >= 0?    100 >= 0?   True  → pass
       ├── discount >= 0? 110 >= 0?   True  → pass
       └── discount <= 100? 110 <= 100? False → FAIL
                                              "Discount cannot exceed 100%"

Compile-Time Assertions with constrained()

For compile-time parameters, use constrained() to enforce rules that are checked before the program runs. If a compile-time parameter violates the constraint, the compiler rejects the code with a clear message.

from builtin.constrained import constrained

fn process[width: Int]():
    constrained[width > 0, "width must be positive"]()
    constrained[width % 2 == 0, "width must be even"]()
    print("Processing with width:", width)

fn main():
    process[8]()    # passes both constraints
    process[0]()    # compile error: "width must be positive"
    process[3]()    # compile error: "width must be even"
Runtime assertion:   checked when the code runs
Compile-time constrained(): checked when the compiler runs
                             → zero runtime cost
                             → error before any code executes

Writing Unit Tests with Assertions

from testing import assert_equal, assert_true

fn fibonacci(n: Int) -> Int:
    if n <= 1:
        return n
    var a = 0
    var b = 1
    for _ in range(2, n + 1):
        var temp = a + b
        a = b
        b = temp
    return b

fn test_fibonacci() raises:
    assert_equal(fibonacci(0), 0,   "fib(0) = 0")
    assert_equal(fibonacci(1), 1,   "fib(1) = 1")
    assert_equal(fibonacci(5), 5,   "fib(5) = 5")
    assert_equal(fibonacci(10), 55, "fib(10) = 55")
    assert_true(fibonacci(20) > fibonacci(19), "Fibonacci is increasing")
    print("All fibonacci tests passed!")

fn main() raises:
    test_fibonacci()

Assertions vs Error Handling

Assertions:                         Error handling:
  For bugs in YOUR code               For problems from OUTSIDE your code
  "This SHOULD never happen"          "This MIGHT happen in production"
  Disable in production (optional)    Always active
  Catch logical errors early          Handle user input, network, files

  assert x > 0   ← programming error   raise Error(...) ← runtime condition

Key Takeaways

Assertions verify that your assumptions hold at specific points in your code. Use assert_true, assert_false, assert_equal, and assert_not_equal from the testing module. Failed assertions halt the program with the message you specified, identifying the bug location immediately. Use constrained() for compile-time parameter validation at zero runtime cost. Assertions document the rules your code depends on and test them continuously during development. Use them heavily in development and testing; they are distinct from error handling, which covers expected runtime failures.

Leave a Comment

Your email address will not be published. Required fields are marked *