Search code examples
rustassertcompile-time

How to make rust compiler prevent me from calling a function with an argument value of zero?


Quite simply, I have a setter method on a struct that sets a field value of type i32 and I don't want to allow zero or negative numbers. I have achieved this with an assert! macro like so:

pub fn set_myfield(&mut self, arg: i32) {
    assert!(arg > 0);
    self.myfield = arg;
}

This appears to cause a panic at runtime if the caller calls set_myfield(0), but gives no error at compile time. Is there any kind of assertion I can write so that consumers of my library will get an error at compile time if their code tries to call this method with a zero or negative input?


Solution

  • In Rust, there is no way to specify a subset of a type's values as a type. However, you can define new types which enforce rules, or use existing ones, and there is even an existing type for the rule you want: std::num::NonZeroI32.

    pub fn set_myfield(&mut self, arg: NonZeroI32) {
        self.myfield = arg.get();
    }
    

    Now, in a sense, this doesn't change anything — there's still going to be a run-time check for constructing NonZeroI32. However, it means that:

    • Your function states its requirement in the type system rather than the documentation.
    • Your function doesn't ever panic.
    • The caller can choose to make the check earlier than when it calls set_myfield, which may simplify error handling paths.

    And, in future versions of Rust, it may be possible to (when applicable) define constants of nonzero types, making everything truly compile-time checked.

    use std::num::NonZeroI32;
    
    const FOUR: NonZeroI32 = NonZeroI32::new(4).unwrap();
    

    (Right now, this code doesn't compile because Option::unwrap() is not available in const evaluation.)