Skip to main content

Runtime Assertions (runtime:assert)

The runtime:assert statement both proves a condition at verification time and traps at runtime if the condition is false.

Syntax

runtime:assert condition;

The verifier checks that condition is provable from preconditions and program state. At runtime, if condition is false, the program traps before the following operation executes.

Dual Behavior

Unlike assert which is erased from compiled output, runtime:assert has both verification and runtime effects:

PhaseBehavior
VerificationProves condition is true (proof obligation)
RuntimeTraps if condition is false

This makes runtime:assert useful when you want mathematical proof and runtime protection as a safety net. For public exported methods, pair caller-facing proof preconditions with entry_requires when the same guard must be enforced before external callers enter the method.

When to Use runtime:assert

Use runtime:assert for safety-critical checks where you want both verification and runtime enforcement:

Guarding Division

persistent actor {
public func div(a : Int, b : Int) : async Int
entry_requires b != 0;
requires b != 0;
ensures result == a / b;
{
runtime:assert b != 0;
a / b
};
}

The verifier proves b != 0 from the precondition. At runtime, the assertion provides defense-in-depth.

Guarding Array Access

persistent actor {
var data : [var Int] = [var 1, 2, 3];

public func get(i : Nat) : async Int
reads data
entry_requires i < data.size();
requires i < data.size();
ensures result == data[i];
{
runtime:assert i < data.size();
data[i]
};
}

The precondition guarantees bounds safety. The runtime assertion documents and enforces this at runtime.

Guarding Option Unwrapping

persistent actor {
public func get(opt : ?Nat) : async Nat
entry_requires opt != null;
requires opt != null;
{
runtime:assert opt != null;
switch opt {
case (?v) v;
case null { 0 };
}
};
}

The verifier knows opt is non-null, and the runtime check enforces this guarantee.

Verification Requirements

The condition must be provable from:

  • Preconditions (requires clauses)
  • Actor invariants
  • Previous assertions in the function
  • Known properties of the program state

If the verifier cannot prove the condition, verification fails.

Provable Assertion

persistent actor {
public func ok(x : Nat) : async Nat
entry_requires x > 0;
requires x > 0;
{
runtime:assert x > 0; // Provable from precondition
x
};
}

This verifies because requires x > 0 implies x > 0.

Unprovable Assertion

persistent actor {
public func bad(x : Nat) : async Nat {
runtime:assert x == 0; // Cannot be proven - x could be any Nat
x
};
}

This fails verification. The function accepts any Nat, so x == 0 is not guaranteed.

Compared to assert

Aspectassertruntime:assert
VerificationProves conditionProves condition
RuntimeNo effect (erased)Traps if false
Use caseDocumentation, proof stepsSafety-critical guards

Use assert for pure documentation and proof guidance. Use runtime:assert when you need runtime enforcement.

Compared to trap

Aspectruntime:assert Ptrap cond
VerificationProves P is trueProves cond is false
RuntimeTraps if P is falseTraps if cond is true
Meaning"P must hold here""cond is an error state"

runtime:assert says "this condition is guaranteed." trap says "this error condition is unreachable."

Restrictions

Not in Pure Functions

Pure functions cannot contain runtime:assert:

// This is rejected:
pure func bad(x : Nat) : Nat {
runtime:assert x > 0; // Error M0240
x
};

Pure functions are side-effect free and used in specifications. Use requires clauses instead:

pure func good(x : Nat) : Nat
requires x > 0;
{
x
};

Not in Lemmas

Lemma functions also reject runtime:assert:

// This is rejected:
lemma func bad() {
runtime:assert true; // Error M0240
};

No old() in Condition

The old() expression captures state at method entry. Since runtime:assert is runtime code (not ghost), you cannot use old() in its condition:

// This is rejected:
public func bad() : async () modifies x {
let prev = x;
x := x + 1;
runtime:assert x == old(x) + 1; // Error: old() not allowed
};

Use a local variable to capture the previous value if you need a runtime comparison, or use old() in an ensures clause or static assert when you only need a proof obligation:

public func good() : async () modifies x {
let prev = x;
x := x + 1;
runtime:assert x == prev + 1; // Works
};

Common Patterns

Precondition Echo

Echo the precondition as a runtime assertion:

public func transfer(amount : Nat) : async ()
modifies balance
entry_requires amount > 0;
requires amount > 0;
{
runtime:assert amount > 0; // Document and enforce
balance := balance - amount;
};

Invariant Checkpoint

Assert an invariant property after state changes:

public func deposit(amount : Nat) : async ()
modifies balance
{
balance := balance + amount;
runtime:assert balance >= amount; // Cannot underflow
};

Safety Guard Before Dangerous Operation

Assert safety before operations that could fail:

public func pop() : async Int
modifies stack
entry_requires stack.size() > 0;
requires stack.size() > 0;
{
runtime:assert stack.size() > 0;
let last = stack.size() - 1;
let value = stack[last];
// ... remove element
value
};

When Verification Fails

If verification fails on a runtime:assert, check:

  1. Missing preconditions - Add a requires clause that guarantees the condition
  2. Incorrect assertion - The condition might not actually hold in all cases
  3. Missing intermediate steps - Add assert statements to guide the proof

A failing runtime:assert means either the code has a bug or the specification is incomplete.

Summary

  • runtime:assert condition proves a property AND traps if false at runtime
  • Must be provable from preconditions, invariants, or program state
  • Use for safety-critical guards (division, bounds, null checks)
  • Cannot appear in pure functions or lemmas
  • Cannot use old() in the condition
  • Provides defense-in-depth: verification catches bugs, runtime catches the unexpected