Live data from Hacker News

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

tratt.net

61–70 of 87 posts

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

#61

Earlier quoted context omitted.

Ok I guess we've all got to learn it at some point. Maybe there should be some kind of test of lessons that "the industry" has learnt for new people. Anyway... 1. There's no semantic different between `assert` and `doAssert`. Does `assert` not "do" the assert? Of course it does. The names are meant to communicate what the functions do, and these fail. It should be called `assert` and `always_assert` or something....…

> There's no semantic different between `assert` and `doAssert`. There is a difference, but I agree that it's very subtle. In english do before a verb adds an emphasis so it's like "assert assert" or "really assert" put in a short easy to type form.

Any subtle difference you might be imagining is completely unrelated to the actual difference between them, and there's absolutely no way you could infer that difference.

They very clearly chose `doAssert` because they already had `assert` and couldn't think of a good name for a second similar-but-different kind of assert.

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

#62

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…

> so a program that packed more state in those top bits would now be broken

You can use pointer masking to avoid this issue - basically you tell the hardware to ignore the top N bits, even if they are part of the virtual address. RISC-V supports this for 7 and 16 top bits. I assume ARM has a similar feature.

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

#63

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…

> so a program that packed more state in those top bits would now be broken You can use pointer masking to avoid this issue - basically you tell the hardware to ignore the top N bits, even if they are part of the virtual address. RISC-V supports this for 7 and 16 top bits. I assume ARM has a similar feature.

Note that x86-64 has no such feature, and requires pointers to be in the canonical form I explained in my previous comment (sorry however for being overly x86-centric in that comment).

Moreover even on ARM/RISC-V the primary reason this feature was added was to use memory tagging to track allocations and detect out-of-bound-access/use-after-free bugs. Exposing those top bits for other usecases will make your language incompatible with that detection mechanism (possibly triggering false-positives in it)

> basically you tell the hardware to ignore the top N bits, even if they are part of the virtual address

You can ignore the top N bits even manually by masking the pointer. The issue arises if you ever get a pointer whose top N bits actually matter (i.e. if masking them off produces a pointer to a different address). If you don't have the guarantee that this will never happen then your pointer masking it wrong.

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

#64

Nim has this too: - `assert` is disabled in unsafe `danger` mode or can be disabled with a flag for performance - `doAssert` cannot be disabled While I've never come across an argument for why there are two types of assert, over time I’ve naturally started using them in the same way as the author.

How are people still making the classic `foo`/`foo_safe_version` mistake? In a supposedly modern language. And they didn't even name the safe version clearly! If the rest of Nim is designed on this level then I hope it never succeeds. Note that Rust got this exactly right. assert, debug_assert. Clear and fail-safe.

Nim is my anti-language... I'd make the opposite language design choice in almost every instance.

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

#65

Earlier quoted context omitted.

> so a program that packed more state in those top bits would now be broken You can use pointer masking to avoid this issue - basically you tell the hardware to ignore the top N bits, even if they are part of the virtual address. RISC-V supports this for 7 and 16 top bits. I assume ARM has a similar feature.

Note that x86-64 has no such feature, and requires pointers to be in the canonical form I explained in my previous comment (sorry however for being overly x86-centric in that comment). Moreover even on ARM/RISC-V the primary reason this feature was added was to use memory tagging to track allocations and detect out-of-bound-access/use-after-free bugs. Exposing those top bits for other usecases will make your language…

I think all your points are answered by the background section of the RISC-V pointer masking extension spec:

> Doing this without hardware support introduces significant overheads since the pointer tag needs to be manually removed for every conventional memory operation. Pointer masking support reduces these overheads.

> It is worth mentioning that while HWASAN is the primary use-case for the current pointer masking extension, a number of other hardware/software features may be implemented leveraging Pointer Masking. Some of these use cases include sandboxing, object type checks and garbage collection bits in runtime systems.

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

#66
post #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.

Yes. I mean, assert spew should probably be maximally informative, which means it's probably going to vomit out a big stack trace. So for heavily used CLIs (especially if the users are other people), it can be nice to handle the bulk of the common errors specifically (by emitting a brief and to the point error message). Bonus points for suggesting a root cause and what to do differently to make it not happen. And this isn't just unnecessary polish -- it's easy for the cause of the error ("/homw/sfink/.config/myapp not found") to get buried in the noise and for the user to not notice when they've fixed one thing and moved on to the next.

But that's only worth it for some CLI tools. For many, I agree that spewing out an assert failure is plenty good enough.

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

#67

I remember someone arguing that disabling assertions in prod is like wearing a life jacket in the harbor but throwing it overboard going to sea. And Moore's Law paid for them years ago.

Some replace asserts with __builtin_unreachable - I guess that would be like filling life jacket with stones?

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

#68

I remember someone arguing that disabling assertions in prod is like wearing a life jacket in the harbor but throwing it overboard going to sea. And Moore's Law paid for them years ago.

Some replace asserts with __builtin_unreachable - I guess that would be like filling life jacket with stones?

More like filling them with beer.

If the ship sinks, they're worse than useless. But since you've decided they'll never be needed, you get more beer for your cruise.

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

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

> Finally I want to point out that using assert is not ERROR checking (file not found, IP address not resolved, TCP connection failed, system resource failed to allocate, etc.) but BUG checking.

> Do not write code for BUGS.

I have nothing to add, but am quoting the above because it is very well put.

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

#70
post #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 i…

It depends on the application. For some applications, a problem in an assert-less production application is easy to reproduce in an assert-ful debug build, because the relevant inputs are easy to capture and replay.

The reason to not default to leaving all assertions and logging enabled is that performance-sensitive applications are pretty common. They're not performance-first, but the performance of any user-facing application matters. If leaving the asserts in provides good enough performance, do that. If dynamically enabled asserts provide good enough performance, do that -- at least you'll be able to quickly retry things. And since different asserts have different costs, do what the article says and distinguish between assert and debug_assert.

Post reply on HN