I’ve been excited about the introduction of NonZeroUsize
and friends in Rust 1.28 - they’re efficient and prevent safety concerns around e.g. divisions by zero, AND prevent APIs from being used the wrong way. Great! However, I feel somewhat strongly that Rust is missing a piece here to make non-zero types be really really useful / ergonomic. Hope this discussion can get us closer to that missing piece.
Background: Over the past few months I’ve designed and used some APIs based on non-zero types (e.g. the tests on fibonacci_codec and this warning ratelimit_meter). Unfortunately, they’re pretty hard to use correctly, and incur either an unnecessary runtime check overhead in Debug mode (see the test case above; thankfully, Release builds optimize away the check; but if you accidentally use 0
, they optimize to a panic), or an unwieldy unsafe
block.
I came up with a work-around with nonzero_ext, and while that feels better, it still seems a bit unwieldy, and the error messages are not very good. I think it would make sense to make the compiler both smarter and to allow users to specify non-zero literals directly, so I’ve written up a draft RFC here:
https://gist.github.com/antifuchs/7530075de2c2e894b97300dfb3ac9920 - my very first rust (draft) RFC!
It’s a writeup of what I think is the closest to a much better state from here: A compile-time checked way to specify literals that’s much less verbose than what we have now. Many alternative approaches exist! Please comment! (-:
(Edit: A kind reader pointed out that it would be good to have here a copy of the Summary & what it’s about! I’ll paste the Summary & guide-level explanation sections so you don’t have to read through all the above)
Summary
Add an extension to the INTEGER_LITERAL
syntax that allows users to specify literals as non-zero unsigned integers. We introduce a new INTEGER_SUFFIX
that starts with n
to indicate non-zero literals. These literals get checked at compile-time to ensure they are not zero.
Guide-level explanation
When using APIs that specify non-zero unsigned integer types, code passing integers literals (like 20
) to these APIs needs to assert that those literals are not zero.
This can be achieved in safe code by converting a u32
integer to a NonZeroU32
integer. Given the definition for a division function for unsigned integers that can never divide by zero:
fn divide_by(dividend: u32, divisor: NonZeroU32) -> u32 { dividend / divisor.get() }
We’d call the function like so:
divide_by(100, NonZeroU32::new(20).expect("inconveivable!"))
However, this long-form conversion performs the check that 20 is not zero at run time, so if somebody should accidentally delete the 2
while editing, the program will still compile and panic at run time.
The easier (and shorter) way to make this assertion is to use the n32
suffix on the integer literal:
divide_by(100, 20n32)
It is a compile-time error to use the literal zero with an n
suffix, so no edits or slips of the finger will result in an accidentally compiling program that errors at run time.