You Must Fix Your Asserts

21 points by gulbanana


goldstein

I agree that just crashing (/ crashing a task, like Rust panics) on assert is often the best behaviour. I can’t agree that using asserts as optimization guides is necessarily better than just compiling them out though. this is for two reasons:

first, arbitrary asserts are often not great for optimization. a lot of conditions are not directly usable by the optimizer. unless you’re asserting something like “this branch will never be taken” directly, performance benefits from peppering random assumptions all over the code are probably not huge.

second, turning asserts into assumptions greatly amplifies the blast radius of a mistake. imagine you have a system that processes some data partitioned by some key: maybe the work is completely separated by project, or by user. let’s say we have an assert in the middle of a computation function that catches some state that should not have been possible. we did some profiling and decided that we can’t afford to keep it in release builds, because the performance impact is too large. if we just disable it, any possible damage to the data would be limited to one project / user, and might even be caught by some other check down the line. on the other hand, if we turn it into UB, the computation might jump into some random code, arbitrarily corrupt the memory, and potentially damage data for every project.

essentially, by choosing unsafe assertions as the default in release builds we’re prematurely optimizing random places in our code in exchange for reducing the chance of localizing the damage if something goes wrong in release.

I think Rust gets it right: it has assert!() that always panics, debug_assert!() that only panics in debug mode, and assert_unchecked() that panics in debug and turns into an optimization hint in release. you use the first by default, the second when the performance impact of panicking is too large, and the last when you actually have demonstrable gains from doing that.