Live data from Hacker News

Rust's Two Kinds of 'Assert' Make for Better Code

tratt.net

71–80 of 87 posts

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#71

I agree with the others here that classic assert statements are in a weird spot. You enable it when testing, in other words when the data flowing through your program is likely to be constrained and lazy. And then turn it off when your program is about to meet real data (production). There's some wrinkle to this where you might want to log certain things in production instead of aborting with an assertion failure. Bu…

Logging systems often have the specificity you're describing, with selectors for modules etc. You could expand a logging system to handle asserting. "Log the current state, which should never be Running."

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#72
post #47
post #37

I don't think I've ever in 30+ years of programming seen a runtime assertion that wouldn't be improved by doing something else instead. Writing unit tests and choosing designs that codify the constraints in the type system are two things that are obvious. The cost of a failing assertion is often incurred at some point where it is most expensive. I know this because I've worked for companies that have charged exorbita…

Respectfully I completely disagree and I think you've got it rather backwards since you've had some bad experiences (understandable). Assert and (unit)tests are completely orthogonal and unrelated things in terms of functionality even though both are aimed at improving the software correctness. Failing an assert in production of course sucks and is costly. But what is more costly is letting the bug slip through and c…

> since you've had some bad experiences

No, I've had great experiences with assertions in code. People have paid my salary because the assertions are invalid and cause more problems than they solved. :D

> Failing an assert in production of course sucks and is costly. But what is more costly is letting the bug slip through and cause hard to diagnose bugs, program incorrectness and even (in some cases) silent address space corruption that will then manifest itself in all kinds of weird issues later on during the program run.

The direct counterpoint to this is that:

Any assertion that validates a runtime invariant can (and IMO should) be converted into a test which covers that same invariant, with coverage information proved by tooling.

This is possible unless the underlying design of the system under test is such that it prevents adequate testing, or your approach to testing is lacking. If you have those problems then asserts are a band-aid on broken practices. Moving quality checks to the left (design / compile time, not runtime) is a generally beneficial practice.

Put another way, I've seen many bugs which should have been caught cheaply early with adequate testing practice, rather than at runtime where they caused system failures. It's a rare bug that I see that that isn't the case.

Perhaps there are points where this broad recommendation doesn't apply. Safety engineering might be one of those, but the problem space of selling someone a widget over the internet rarely has that same level of need for runtime invariant testing that sending a rocket to space might.

---

On a different side of this, I do think that system level assertions (i.e. real code paths that result in actions not `debug_assert!` calls which result in crashing) can belong in systems to check that some process has reached a specific state. I prefer systems to be designed that don't (provably) crash ever.

---

A third side to this is that assertions are code too. They are a place which is rarely if ever tested (and is generally impossible to test because they cover invariants). This means that they're an unmitigatable risk to your system.

A thought experiment for you, what if LeftPad[1] (instead of being deleted) added an assertion that the total number of characters was [1]: https://en.wikipedia.org/wiki/Npm_left-pad_incident

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#73
post #72
post #47

Earlier quoted context omitted.

Respectfully I completely disagree and I think you've got it rather backwards since you've had some bad experiences (understandable). Assert and (unit)tests are completely orthogonal and unrelated things in terms of functionality even though both are aimed at improving the software correctness. Failing an assert in production of course sucks and is costly. But what is more costly is letting the bug slip through and c…

> since you've had some bad experiences No, I've had great experiences with assertions in code. People have paid my salary because the assertions are invalid and cause more problems than they solved. :D > Failing an assert in production of course sucks and is costly. But what is more costly is letting the bug slip through and cause hard to diagnose bugs, program incorrectness and even (in some cases) silent address s…

> Any assertion that validates a runtime invariant can (and IMO should) be converted into a test which covers that same invariant, with coverage information proved by tooling.

How would you unit test a loop invariant [0]?

[0] https://en.wikipedia.org/wiki/Loop_invariant

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#74

Earlier quoted context omitted.

In terms of category theory, what we would need is subtraction and division types on top of product and sum types. So a 32-bit integer is the product of 32 two-state bit types. Something akin to NonZero could be defined as that type minus one state, such that there are now 4294967296 - 1 representable values. Similarly, pointer types on some machines always have some bits set to 0 due to hardware constraints. These c…

> So a 32-bit integer is the product of 32 two-state bit types. Something akin to NonZero could be defined as that type minus one state, such that there are now 4294967296 - 1 representable values. I can see a couple of problem with this approach: - you need to be able to talk about bit-level types, which contradicts the common assumption that all types are addressable, i.e. whose size and alignment is some positive…

My thinking was that this would be up to the compiler, where you’d have to specify the target CPU model for it to take advantage of the feature. Similarly, byte code VM could use this with runtime detection of the CPU properties.

This would work best in a high level language where the specific bit layout of struct types is not defined (by default). Rust is one such language, but this would also work with .NET and JVM languages.

One approach is that integers are represented as A x Pointer types could be internally represented as ordinary integers, or aligned non-null pointers on some architectures would be “0 x This could have no effect on the emitted code, but the compiler would be free to utilise the spare low bits or the non-zero value if it chose to do so. Rust does this in a few hard-coded scenarios, but a more complete type model would allow more flexibility. I.e.: it could pack the enum discriminator and the value in there if the enum has only pointer types as values.

Conversely, the high level language can use this for type checks to give better error messages.

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#75
post #73
post #72

Earlier quoted context omitted.

> since you've had some bad experiences No, I've had great experiences with assertions in code. People have paid my salary because the assertions are invalid and cause more problems than they solved. :D > Failing an assert in production of course sucks and is costly. But what is more costly is letting the bug slip through and cause hard to diagnose bugs, program incorrectness and even (in some cases) silent address s…

> Any assertion that validates a runtime invariant can (and IMO should) be converted into a test which covers that same invariant, with coverage information proved by tooling. How would you unit test a loop invariant [0]? [0] https://en.wikipedia.org/wiki/Loop_invariant

The better question is why should you need to (test the invariant)?

If your tests cover all branches of a loop, then it doesn't matter if a loop invariant is violated within the loop. Write tests that exercise the {0, 1, Some, Bounds} branches, verify that the result is correct. Then go on with your life knowing that there's no possible way that the code breaks without either failing the tests or changing the branch coverage.

Put another way (and taking the max() implementation from the wikipedia article. If I implement the max function in a way where in the first loop iteration I add 1 and in the last I subtract 1, then the loop invariant doesn't hold but the result is correct. The assertion approach fails, but the test approach succeeds because I have tests for {0, 1, Some} iterations.

: It would be silly to do this, but I'm trying to present a minimal counter-point here. You could extrapolate this type of change to a change made for performance purposes which are less obviously correct, and which may cause this type of internal invariant to be false. The argument is the same.

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#76
post #13

Earlier quoted context omitted.

It would be interesting if a language allowed control flow to jump between catches and exceptions with named sort of exceptions. E.g., imagine in this example that the code code throw an invalid index exception, some calling code could catch that, and supply a new index, and control flow would resume from the throw expression. This would be a complete mess, but it would be interesting nonetheless :)

That is literally what Common Lisp has and GP describes… https://en.m.wikibooks.org/wiki/Common_Lisp/Advanced_topics/...

I realize you could probably build this in lisp, but this (by default) seems to be missing the part about jumping back to where the exception was raised, instead of resuming flow control from where the restart was defined, iiuc.

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#77

Earlier quoted context omitted.

If you created a custom NonZero type, how would the Rust compiler figure out how to optimize that? How would one communicate properties/traits of a custom type (like a non-zero unit) that a compiler can leverage to optimize ( in general, for any programming language )?

I don't think the mechanics of being able to convey a niche are stable/developer-accessible, but NonZero is a type provided by the standard library: https://doc.rust-lang.org/std/num/struct.NonZero.html An example of the niche optimisations is in that link; if a 32bit number is NonZero, you can put that in an Option, and your Option > will be the same size as a normal u32.

An interesting project for niche types: https://github.com/rick-de-water/nonany

It's not perfect but does allow some flexibility.

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#78
post #72
post #47

Earlier quoted context omitted.

Respectfully I completely disagree and I think you've got it rather backwards since you've had some bad experiences (understandable). Assert and (unit)tests are completely orthogonal and unrelated things in terms of functionality even though both are aimed at improving the software correctness. Failing an assert in production of course sucks and is costly. But what is more costly is letting the bug slip through and c…

> since you've had some bad experiences No, I've had great experiences with assertions in code. People have paid my salary because the assertions are invalid and cause more problems than they solved. :D > Failing an assert in production of course sucks and is costly. But what is more costly is letting the bug slip through and cause hard to diagnose bugs, program incorrectness and even (in some cases) silent address s…

Basically what you're saying is

"If you never have an accident you don't need seat belts in the car, and since we test drove the vehicle in the factory parking lot and didn't have an accident we decided not to have the seat belts".

Point being asserts are the final back stop. Your unit tests don't help you validate/test any real execution instance or function call that happens right now in production.

You're right in the sense though that if you have some functionality that is only ever called in a way that all the calls known ahead of time and you can test all the possible code paths and inputs then you can get away without asserts. But I find that these scenarios don't manifest themselves that often. Most code executions are impossible to know 100% ahead of time and your unit tests are only ever testing a subset of all possible inputs and execution flows and even if you have 100% bullet proof coverage right now in the future you probably won't and then you're just one innocent change away from letting bugs slip through in your production runs.

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#79

Earlier quoted context omitted.

> So a 32-bit integer is the product of 32 two-state bit types. Something akin to NonZero could be defined as that type minus one state, such that there are now 4294967296 - 1 representable values. I can see a couple of problem with this approach: - you need to be able to talk about bit-level types, which contradicts the common assumption that all types are addressable, i.e. whose size and alignment is some positive…

My thinking was that this would be up to the compiler, where you’d have to specify the target CPU model for it to take advantage of the feature. Similarly, byte code VM could use this with runtime detection of the CPU properties. This would work best in a high level language where the specific bit layout of struct types is not defined (by default). Rust is one such language, but this would also work with .NET and JVM…

I'm not sure where subtraction and division types come into play then. If all of this is done internally in the compiler then this should part of the layout system/implementation, and not exposed to the user in the type system.

> where you’d have to specify the target CPU model

I would be wary of doing this unless you're sure your executable will only ever run on that CPU model.

> This would work best in a high level language where the specific bit layout of struct types is not defined (by default). Rust is one such language

Note that Rust does leak some implicit assumption about struct layouts. For example you can always get a pointer to a struct field, meaning a field can't be e.g. a single bit, it must be an non-negative amount of bytes (so for example the 32-bit number as product type of 32 1-bit numbers would not work if you use a struct as the product type).

> or aligned non-null pointers on some architectures would be “0 I wonder which architecture requires all pointers to have an alignment of 8.

Re: Rust's Two Kinds of 'Assert' Make for Better Code

#80
post #78
post #72

Earlier quoted context omitted.

> since you've had some bad experiences No, I've had great experiences with assertions in code. People have paid my salary because the assertions are invalid and cause more problems than they solved. :D > Failing an assert in production of course sucks and is costly. But what is more costly is letting the bug slip through and cause hard to diagnose bugs, program incorrectness and even (in some cases) silent address s…

Basically what you're saying is "If you never have an accident you don't need seat belts in the car, and since we test drove the vehicle in the factory parking lot and didn't have an accident we decided not to have the seat belts". Point being asserts are the final back stop. Your unit tests don't help you validate/test any real execution instance or function call that happens right now in production. You're right in…

> Basically what you're saying is

> "If you never have an accident you don't need seat belts in the car, and since we test drove the vehicle in the factory parking lot and didn't have an accident we decided not to have the seat belts".

Would you trust a manufacturer that added seat belts, but never tested they worked? That's what a runtime assertion is. If it can never fail unless there's a bug, then they can never be tested...

Assertion failure modes are also problematic. Their entire mechanism is blow up and stop running the program. Would you trust a car which crashed if a seatbelt was unplugged?

The test of my assertion is: show me some real-ish code where you think runtime assertions are useful (preferably in backend / web code not a kernel or such).

Post reply on HN