Assert
An assertion states something the program relies on, and checks it while the program runs. It is a panic with a condition attached: when the condition holds, nothing happens; when it does not, the program stops.
Assertions are for the same kind of problem as Panic — a bug, not bad input. They make an assumption visible in the code, and make sure that if it is ever wrong, the program says so at once instead of carrying on with nonsense.
Assert
The median of a list is its middle score, and an empty list has no middle:
Assert(scores.length > 0, "a median needs at least one score");
Call Median with an empty slice and the program stops exactly like a Panic, reporting the message and where the assertion is:
Assertion failed: a median needs at least one score
at Median (Src/Main.rux:43:5)
Write the message as what was expected, since the failing value is not printed. "a median needs at least one score" tells the reader the rule that was broken; "empty list" would only describe the symptom.
DebugAssert
Core has two assertions, and the difference is the build:
DebugAssert(InOrder(scores), "the scores are sorted");
Assert | DebugAssert | |
|---|---|---|
Debug build (rux run) | checked | checked |
Release build (rux run --release) | checked | removed — the condition is not evaluated |
| Use it for | a promise that must hold in the program you ship | a check worth making while developing, too slow to pay for when shipped |
InOrder is a whole pass over the scores. That is worth doing while developing, but it would turn a cheap lookup into a slow one on every call in a shipped program — so it sits in a DebugAssert.
Removed means not evaluated
InOrder prints a line so you can see the difference. Run the program both ways and the line appears only in the debug build — in a release build the call never happens at all.
That has two consequences. An expensive check costs nothing when shipped. And a condition with a side effect loses that effect: anything a DebugAssert's condition does is gone from the release build.
It also means a release build no longer catches the mistake the check was there for. Give Median the unsorted scores [3, 8, 5, 13, 21], and the debug build stops with Assertion failed: the scores are sorted, while the release build quietly prints median: 5.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// An assertion states something the program relies on, and checks it while
// the program runs:
//
// Assert(scores.length > 0, "a median needs at least one score");
//
// When the condition holds, nothing happens. When it does not, the program
// stops exactly like a `Panic`, reporting the message and where it was:
//
// Assertion failed: a median needs at least one score
// at Median (Src/Main.rux:43:5)
//
// Write the message as what was expected, since the failing value is not
// printed.
//
// `Core` has two of them, and the difference is the build:
//
// - `Assert` is checked in every build. Use it for a promise that must hold
// in the program you ship.
// - `DebugAssert` is checked in a debug build (`rux run`) and removed from a
// release build (`rux run --release`). Removed means its condition is not
// even evaluated, so an expensive check costs nothing when shipped — and a
// condition with a side effect loses that effect.
//
// `InOrder` below prints a line so you can see that difference: run the
// program both ways and the line appears only in the debug build.
import Core::{ Assert, DebugAssert };
import Io::PrintLine;
// A whole pass over the scores: worth checking while developing, too slow to
// pay for on every call in a shipped program.
func InOrder(scores: int[..]) -> bool {
PrintLine("(checking that the scores are in order)");
for i in 1..scores.length {
if scores[i - 1] > scores[i] {
return false;
}
}
return true;
}
// The middle score of a sorted list.
func Median(scores: int[..]) -> int {
Assert(scores.length > 0, "a median needs at least one score");
DebugAssert(InOrder(scores), "the scores are sorted");
return scores[scores.length / 2];
}
func Main() -> int {
let scores = [3, 5, 8, 13, 21];
PrintLine("median: {}", Median(scores));
return 0;
}
Besides Io, its Rux.toml lists Core under [Dependencies].
Run it
cd Examples/Errors/Assert
rux run
(checking that the scores are in order)
median: 8
A release build drops the DebugAssert, so its check never runs:
rux run --release
median: 8
Common mistakes
A condition such as
DebugAssert(Load(settings), "...") does its loading in a debug build and not at all in a release build. Do the work first, store the result, and assert on the stored value.Assert(scores.length, "...") is error: argument 1 to 'Assert' has type 'uint64', but parameter 'condition' requires 'bool8'. Write the comparison out: scores.length > 0.Like
Panic, an assertion is for bugs. Input that can be wrong in a working program deserves a fallible and a helpful message, not a stopped program.Try it yourself
- Call
Median(scores[0..0])— an empty slice of the scores — and read the assertion message. - Change the scores to
[3, 8, 5, 13, 21]and run withrux runandrux run --release. Compare the two. - Add an
Assertthat every score is between 0 and 100, and decide whether it should be anAssertor aDebugAssert.
Learn more
- Assert and #build in the API reference
- Build mode — debug and release builds, and how a program can tell them apart
- rux run — the
--releaseflag - Panic — the same stop, without a condition
9.15 Panic
Stop the program at once with Core::Panic when something that cannot happen has happened.
Overview
A value that is one of several types: A | B. Five lessons on building sums, matching them by type and by subset, asking which member is active with is, and passing a smaller sum where a larger one is expected.