Live data from Hacker News

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

tratt.net

21–30 of 87 posts

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

#21

Asserts are convenient, but every time I encounter them I ask "Can the compiler prove this can't happen?" In the example of "min(ages) > 0", making age a NonZero type renders the assert unnecessary. Rust even has some fancy perf optimizations it can do with that information. It's a win all around.

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 )?

In general, you need a type system that supports sets of integer values, e.g. range(2, 7) or set(3, 5, 7). Rust doesn't support that unfortunately so it has a special annotation instead to make NonZero work.

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

#22
post #7

Is it common to use parenthesis for python assert statements?

I would say no. In fact it is risky, because if you try to add an assertion message inside the parens you’re now asserting a non-empty tuple which is always truthy. Though obviously you might know enough to not make this error, and want the parens for consistency when asserts need to be multiline.

tratt might also be doing that more for consistency with the desugaring than as a routine behaviour.

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

#23

Asserts are convenient, but every time I encounter them I ask "Can the compiler prove this can't happen?" In the example of "min(ages) > 0", making age a NonZero type renders the assert unnecessary. Rust even has some fancy perf optimizations it can do with that information. It's a win all around.

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 )?

In general such a thing is written with a wrapper type which disappear at compilation (a kind of stricter type alias).

In rust might be a NewType: https://www.howtocodeit.com/articles/ultimate-guide-rust-new...

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

#24
Similarly python in optimised mode with -O or -OO flags will disable asserts.

I see asserts as a less temporary version of print() based debugging. Sometimes very useful as a quick and dirty fix, but 9 times out of 10 you’re better off with some combination of a real debugger, unit tests, tracing/logging, and better typing or validation.

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

#25
post #13
post #9

Common Lisp does assertions right: you can let ` assert ' provide a restart which allows the user to fix a problem when it crops up. For example: (assert ( That will print a message when the index is too large and give you the option of providing another index.

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 :)

You can do it easily in any language with coroutines, for example Lua.

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

#26
post #16

> These days I thus view asserts as falling into two categories: > 1. Checking problem domain assumptions. > 2. Checking internal assumptions. (1) is the category of assert that should not be an assert . That is an error to be handled, not asserted. Ok, to be fair, (1) is really a combination of two categories: (1a) assumptions about uncontrolled external input, and (1b) assumptions about supposedly controlled or kno…

Yeah I do this kind of fuzz testing all the time. Its an incredible way to test code.

For fuzz testing I go even further with asserts. I usually also write a function called dbg_check(), which actively goes through all internal data and checks that all the internal invariants hold. Eg, in a b-tree, the depth should be the same for all children, children should be in order, width of all nodes is between n/2-n, and so on.

If anything breaks during fuzz testing (which is almost guaranteed), you want the program to crash as soon as possible - since that makes it much easier to debug. I'll wrap a lot of methods which modify the data structure in calls to dbg_check, calling it both before and after making changes. If dbg_check passes before a function runs, but fails afterwards - then I have a surefire way to narrow in on the buggy behaviour so I can fix it.

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

#27
The distinction between debug and release mode wrt my code (assertions, debug logging) was always confusing to me. The experience told me uncountable times that “release” (iow “prod”) is where you want debug information to be produced, cause that’s where your code meets full-blown reality. Otherwise your system silently breaks in production after few weeks, and two weeks later someone will ask you why and to repair it asap. Good luck investigating it without megabytes of the debug info. And don’t expect that you’ll get only definitive questions. In “who’s right” and disputes the question you receive will often have a great share of uncertainty.

Assertions are basically opposite to that. Useless in development because you watch/test closely anyway and useless in “release” because they vanish.

As I rarely write #CLK-level performance-required code - like most developers I believe - and mostly create systems that just do things rather than doing things in really tight loops many times a second, I always leave as much debug info and explicit failure modes as it is reasonable in my production code, so that the few-weeks failure would be immediate and explained in the logs (“reasonable” being readable without holding pgdn and not filling the ssd just after a week). It doesn’t mean that the system must crash hard as in sigsegv, ofc. It means it reports and logs all(!) steps and errors as they occur in a form that is obviously greppable by all important params (client id, task id, module tag, etc), and it stops a logical process in which an inconsistency occurred from advancing it further. If someone asks you later why it happened or what happened at the specific time with a specific process, you always have the answer almost immediately.

Tldr. Unless you write performance-first systems, i.e. you have performance requirements document in any form to follow, don’t turn off assertions and do log everything. You’ll thank yourself later.

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

#28
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/...

It is always fun when people learn some of the things that common lisp has done for a long time.

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

#29
post #16

> These days I thus view asserts as falling into two categories: > 1. Checking problem domain assumptions. > 2. Checking internal assumptions. (1) is the category of assert that should not be an assert . That is an error to be handled, not asserted. Ok, to be fair, (1) is really a combination of two categories: (1a) assumptions about uncontrolled external input, and (1b) assumptions about supposedly controlled or kno…

I fully agree with you. Though I would add that it a little depends on the type of program. A CLI program's lazy, but often sufficient way of handling erroneous input are asserts (that are still enabled in release). In GUI app it shouldn't happen.

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

#30
One point I didn't see mentioned that asserts can be used by the compiler to enable certain optimisations.

For instance, if you assert that an incoming index into a function is within the bounds of a vector, then during the rest of the function the compiler can elide any bounds checking.

Post reply on HN