HN Debrief

Assert(): A Modern How To

  • Programming
  • Developer Tools
  • Software Engineering

The post is a how-to for modern assertions and pushes a broader role for `assert` than the old “debug only” view. It frames assertions as a way to protect correctness, safety, development, and documentation, with production use on the table. The pushback is that this collapses several different tools into one word. Most people landed on a sharper definition: assertions are for internal invariants that should be impossible to violate during valid execution, while invalid user input, library misuse, and expected system-call failures need ordinary error handling, exceptions, or explicit contracts.

Treat `assert` as a check for impossible internal states, not as a general-purpose error handler. If your code needs to survive bad inputs or external failures, model invariants in types where you can, use normal runtime checks where you must, and make sure any assertion failure yields actionable crash data or a supervised restart path.

Discussion mood

Mostly favorable toward production assertions in a narrow sense, but frustrated with the article for being conceptually sloppy. People liked treating invariant violations as fatal, yet strongly rejected using `assert` as a substitute for validation, exceptions, or policy decisions that depend on runtime context.

Key insights

  1. 01

    Encode invariants in types first

    Making invalid states harder to construct cuts off a lot of assertion use before runtime. The concrete suggestion was a "Parse, Don't Validate" approach where checks live in constructors and the resulting type carries the guarantee, such as a non-empty collection or domain-specific ID type. That shifts correctness from scattered defensive checks into APIs and data models. It also makes assertions the backup tool for invariants the language cannot express, instead of the first line of defense.

    When you keep writing the same assertion at call sites, turn that condition into a type or constructor rule. Review your core domain values for places where a wrapper type or immutable record would eliminate repeated runtime checks.

      Attribution:
    • foo42 #1
    • sshine #1
    • Eldt #1
  2. 02

    Crash-only works if observability survives the crash

    Letting an assertion abort the process is only sane when the system can restart from a known good state and you still keep the evidence. The practical advice here was old-school but sharp: coredumps, symbol files, linker maps, and dedicated in-memory debug buffers often beat fancy logging once state is corrupted. Restartability without postmortem context just turns invariant failures into intermittent mysteries.

    If you want fatal assertions in production, test the full failure path. Confirm you get a restart, a usable stack, symbols, and enough preserved state to debug after the fact.

      Attribution:
    • iTokio #1
    • delusional #1
    • rramadass #1
  3. 03

    A good assert should break into the debugger

    An assertion mechanism should stop immediately in a debugger and otherwise abort with useful context. That preserves the failing state before extra logging or cleanup code mutates it further. The comments pointed to long-standing `int3` style breakpoints and the new `std::breakpoint` in C++26 as the standardized version of that workflow.

    If you maintain native code or runtime tooling, make your assertion path debugger-aware. Prefer a direct trap in debug sessions and a hard abort with diagnostics in unattended runs.

      Attribution:
    • eps #1
    • tom_ #1
    • rramadass #1
  4. 04

    Assertions are not validation or exceptions

    The most precise framing was that assertions cover conditions that are impossible to fail except through a bug, while exceptions and returnable errors cover conditions that can fail during normal operation. That sounds semantic, but it changes API design. A library cannot safely `assert` on caller-controlled bad input unless it explicitly wants to hand callers a process kill switch. Contract systems sit between the two by making preconditions explicit without pretending every violation is an internal impossibility.

    Audit any `assert` that touches external input, I/O, or caller-provided values. Most of those should become returned errors, exceptions, or explicit contract checks depending on who owns the failure policy.

      Attribution:
    • dicroce #1
    • chuckadams #1
    • rramadass #1
    • klibertp #1
  5. 05

    Runtime context decides whether fatal asserts are acceptable

    Whether a production assertion is the right tool depends less on code style than on deployment model. A supervised server can often treat invariant failure as a local crash and recover, while an interactive app, a safety-critical system, or a reusable library may need a very different failure mode. That is why several people wanted the article to start with semantics and policy instead of usage tips. Without that, advice about assertions floats free of the environment that makes it safe or reckless.

    Set assertion policy per component, not per language. For each binary or library, decide up front whether assertion failure means restart, fail-safe mode, surfaced exception, or developer-only breakage.

      Attribution:
    • RossBencina #1
    • klibertp #1

Against the grain

  1. 01

    Exceptions fit C++ better than asserts

    In C++, ordinary exception handling was presented as the more practical default for checking function parameter ranges and recoverable failures. That view pushes assertions into a much smaller corner than the rest of the conversation did. It is a useful reminder that language idioms and ecosystem norms shape what counts as maintainable error handling.

    Do not import an assertion-heavy style into a language whose tooling and libraries assume exceptions or result values. Match your failure mechanism to the host ecosystem or your APIs will feel hostile.

      Attribution:
    • dicroce #1
  2. 02

    Test assertions and runtime assertions are different tools

    Using the same word for unit test assertions and runtime assertions hides a real difference. Test assertions often need to report failures without aborting the entire suite, while runtime assertions are usually about halting on broken invariants. Treating them as one concept muddles both API design and failure expectations.

    Keep your testing assertion API separate from your production assertion API. They serve different reporting and control-flow needs, and combining them will make both worse.

      Attribution:
    • crabbone #1
    • twhitmore #1

In plain english

assert
A programming check that verifies a condition the programmer believes must be true at that point in the code.
C++26
The upcoming 2026 version of the C++ language standard.
invariant
A condition about program state that is supposed to remain true throughout execution or at specific boundaries.
std::breakpoint
A C++26 standard library facility for triggering a debugger break in a portable way.

Reference links

Programming models and contracts

  • Parse, Don't Validate
    Referenced as the clearest alternative to scattered runtime assertions by moving checks into constructors and types.
  • It Takes Two to Contract
    Cited as an example of how types and assertions can complement each other in a real system.

Debugger and language references

Testing formats

  • Test Anything Protocol
    Mentioned because the article's suggested assertion API resembles TAP's `ok(condition, message)` style.

Related Hacker News discussion