Live data from Hacker News

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

tratt.net

81–87 of 87 posts

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

#81
post #80
post #78

Earlier quoted context omitted.

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 b…

Who says they can't be tested? Of course they can be tested, that's just a question of the testing tools being able run the process and trigger the assert and then realize that the process has in fact asserted and exited. If you think this can't be done then you really need to look for better tools. As an example if you program in C++ with boost.test you can even test that your code doesn't compile (can be useful for templates occasionally). That being said I find that at least my asserts are most of the time rather self explanatory such as checking against array/vector size and they don't really require specific 'testing'.

(example here: https://github.com/ensisoft/detonator/blob/master/game/tilem...)

"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)."

That's simple, in C++ (that I program mostly in) any time letting the code execute would lead to undefined behavior, 10 times out 10 I prefer a controlled abort (an assert) with a core dump. For example going out of bounds on an array, what are your options? Pretend nothing is wrong, return a default value, throw an exception? All you can do with any of these options is to mask the actual BUG and cause Nth degree bugs down the road where the caller does something wrong since it it already went completely off track already. When 1+1=2 no longer holds it's best to stop.

"Would you trust a car which crashed if a seatbelt was unplugged?"

Assuming that by "crash" you really mean "controlled abort" (which is what an assert is, a controlled abort) yes, I would prefer my car would tell me in some controlled way when the seat belts no longer work rather than silently let me continue.

But that's not the same thing, seat belt not being unplugged isn't a BUG, it's a condition that the car software needs to be able to handle. You might be confused here because many people mix up logical error handling with BUGS.

"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..."

Yes who knows, any individual seat belt may malfunction but still the concept is much better than not having any. We don't go and say "oh, because any single seat belt might be broken it's pointless to have them at all". The same way we don't say "oh because some assert can be wrong (check the wrong thing or the wrong condition etc) it's pointless to have/use them at all". That would be just absurd.

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

#82
post #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."

Thanks. Yeah, I've also been having similar thoughts for logging systems. In particular right now we (at my job) configure the log level through compile-time switches. It would be nicer to be able to toggle things dynamically. Yes, including per module instead of just "debug" or "info"

This is Spring Boot so it seems possible.

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

#83
post #81
post #80

Earlier quoted context omitted.

> 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 b…

Who says they can't be tested? Of course they can be tested, that's just a question of the testing tools being able run the process and trigger the assert and then realize that the process has in fact asserted and exited. If you think this can't be done then you really need to look for better tools. As an example if you program in C++ with boost.test you can even test that your code doesn't compile (can be useful for…

I think we're really talking past each other at this point, so I'm probably not going to respond more on this. Maybe in C++ where you don't have better techniques available, then assertions *are* the best tool you can reach for for this sort of thing. In many other languages however we do have better options. These should chosen over using assertions when possible as the outcome is significantly better.

> For example going out of bounds on an array, what are your options? Pretend nothing is wrong, return a default value, throw an exception?

The article is talking about assertions in rust. The answer to that question in rust is to use `.get()` which returns `Option`. This moves the condition where the array index is outside the bounds into a structured result rather than causing an application crash. An assertion that crashes the program would be useless there, as the language makes the type of error one that is idiomatically avoided. This (in addition to testing) is part of my point. Dig deep into the implementation of this in the std lib and there's no assertion, just a bounds check which either returns `Some(value)` or `None`.

The part I'm saying is problematic is not the check part of the assertion, it's the crashing part. Write software that avoids needing to crash by proving that the scenarios where invariants not invalid don't exist. When you do that any assertions which you include are code paths which are impossible to ever hit. This is by definition.

Expanding on the article example, it requires that the `youngest` variable is always >= 0. Just define that as `u8` and let the compiler be your check. You never need an assertion to test a tautology.

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

#84
post #76

Earlier quoted context omitted.

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.

There is nothing to build, restarts act on the location where the condition was raised. In a conditions system unwinding is a restart, conditions don't unwind before running handlers.

The ability to update a value would make no sense otherwise.

I repeat, what you describe is literally how common-lisp works. Today. And how it has worked for 40 odd years. Also smalltalk.

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

#85
post #76

Earlier quoted context omitted.

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.

https://cuis-smalltalk.github.io/TheCuisBook/Inspecting-the-...

https://cuis-smalltalk.github.io/TheCuisBook/The-Debugger.ht...

https://cuis-smalltalk.github.io/TheCuisBook/Halt_0021.html

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

#86
post #75
post #73

Earlier quoted context omitted.

> 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…

I mean the entire point of even having loop invariants is that you provably know what the properties of the program state are after the loop.

An assertion can tell you if one of your assumptions is incorrect.

Combined with fuzz testing, you can have much higher confidence that your code is correct.

This is a stronger guarantee of program correctness than any set of unit tests you're likely to write.

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

#87

Earlier quoted context omitted.

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…

> I wonder which architecture requires all pointers to have an alignment of 8.

Many bytecode VMs in 64-bit mode do this by default! They may not be forced to align pointers by the CPU hardware, but they do it anyway for various reasons. Many (non-x86) CPUs require aligned pointers to either 32- or 64-bit boundaries.

Post reply on HN