13 comments

[ 2.9 ms ] story [ 30.2 ms ] thread
> Imagine a big codebase with this somewhere in it:

    fn processThing(thing: Thing) void {
       // this function must always be invoked on
       // a thing that has already been started
       assert(thing.is_started);
   
       // ...
    }
I know you mentioned fuzzing earlier in the article but seriously, fuzzing deserves an extra mention after asking me to imagine that.
I really like the Oracle tutorial on Java asserts.

https://docs.oracle.com/javase/8/docs/technotes/guides/langu...

And I'm pretty happy with its design considering its age.

Notably this is not a function call and indeed things are not called unless you enable it. Contrast with Zig. So I guess you will only suffer from code bloat if you never enable them.

The tutorial mentions the dangers of side effects. But it also mentions how to use them for more complicated assertions. That's natural since you'll want something like that when you need to check a post-condition.

Programming assertions get joked on because of, ahem.

- Step one: Turn on extra checks in test environments where the stakes are low

- Step two: Turn them off in production (with realistic data because prod eq. reality) to save cycles

And that seems to be partially accurate. However the truly interesting assertions can test complex conditions that might break complexity (Big O) contracts. Like a private mininum function that is advertised as O(1) on account of taking a sorted list. But there is no type guarantee that the list is sorted. So you assert that it is sorted. But that breaks the complexity contract.

Overall I have not used assertions in Java for trivial conditions in like five years. They're better deployd for something more complex than that.

Then there is the whole thing about -- and more topics to be sure -- crashing the whole application or not. That's not necessarily great for us regular Java programmers. However we can (though discoraged) catch AssertionError if we want to.

Few Java programmers are using assertions, the two most important reasons being that it’s difficult to reliably ensure they are enabled in production, and secondly that an Error tends to be too disruptive. Instead, most programmers use always-on defensive checks like requireNonNull() that throw RuntimeExceptions.
Asserts in any language must not be added willy-nilly but following a specific methodology viz. Design By Contract (DbC) - https://en.wikipedia.org/wiki/Design_by_contract

DbC was first introduced in Eiffel but the ideas can be used in any language. See the following;

1) Design by Contract and Assertions - https://www.eiffel.org/doc/solutions/Design_by_Contract_and_...

2) Applying "Design by Contract" (pdf) - https://se.inf.ethz.ch/~meyer/publications/computer/contract...

3) Also see the book; Persuasive Programming by Jerud Mead and Anil Shinde which uses asserts systematically to write the proof along with code.

Finally, DbC using asserts is now even more important with AI generated code since it allows one to map the specification to generated code.

(comment deleted)
There's more to design-by-contract than asserts, and DbC isn't the only way to use asserts. Most languages lack proper support for design-by-contract.

(For what it's worth I didn't downvote you.)

As a contrast to Zig: C3 is strongly invested in contracts and is able to use them for static analysis at compile time to some degree.
Right. I remember the C3 thread from a few months ago - https://news.ycombinator.com/item?id=46478647

A user raised some confusion with the wording about Contracts in your documentation which was not answered and is still there. I think it should be fixed since there are lots of people who are confused about contracts and how it differs from error handling. Add to this people's confusion about asserts at runtime and how the optimizer may mess it up and you have a veritable rat's nest of confused thoughts/opinions.

Is your approach to static analysis/verification something like what Frama-C does? I had always wished somebody would wrap C/C++ with a full blown static analysis/verification plus runtime framework and make everything into a single integrated whole like for example the Dafny language. Everything should be doable in a single implementation language with annotations as needed.

I believe this is just about the behavior of std.debug.assert.

You can pretty easily have a different kind of assert that disappears in release builds (if you want).

I recently worked for a company with a large C++ codebase. For many years they relied entirely on asserts in QA builds to catch errors, no unit testing or production asserts. When I joined about three years ago they had just started to pick up unit testing, and would be enabling asserts in production “soon” (once all the most common ones had been fixed).

I left a little over a month ago, and the status on that was still “soon”. Some bad practices early on are just really hard to dig yourself out of.

Tying asserts to optimization is bad design. Asserts are for debugging; you write an assert when you are not 100% confident that it holds, or there is a risk of it not holding in the future even if it holds now.

The "assert" word is not a great choice, but it's entrenched that way. In a debate, you'd not want to assert something (present it as a fact) if you're not sure.

Optimization promises have the same form; they are also assertions, but with a different purpose. They can use the same syntax, but some different word.

The word assert is attractive for this purpose: I know for sure something is true, and I'm communicating it to the compiler; thus, I am asserting something.

The designer must not be thereby seduced into conflating debugging assertions with optimization promises.

I understand that in this case they have a safety switch for assertions, but conflating debug assertions with optimization promises by means of a switch is still a poor design.

You want that switch on a case-by-case basis for each individual assertion, and the easiest way for that is to just have a different identifier.

Asserts in my Rust code use a custom runtime macro "assert_eq_as_result" which does what the article is describing, by returning Rust Ok or Rust Error.

The Rust crate: https://crates.io/crates/assertables

All the macros have forms for different outcomes:

    assert_gt!(1, 2) // panic
    assert_gt_as_result!(1, 2) return Result (Ok or Error)
    debug_assert_gt!(a, b) // panic when running in debug mode
If anyone here wants to help me port it to Zig, I'm happy to do it.
Very interesting write-up, thank you! I never really considered that compilers can also use asserts to improve performance, but it does make sense.