如何让rust编译器阻止我调用参数值为零的函数?

egmofgnx  于 2023-04-30  发布在  其他
关注(0)|答案(2)|浏览(118)

很简单,我在一个结构体上有一个setter方法,它设置了一个i32类型的字段值,我不想允许零或负数。我用assert!宏实现了这一点,如下所示:

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

如果调用者调用set_myfield(0),但在编译时没有给出错误,这似乎会在运行时导致死机。是否有任何类型的Assert,我可以写,使我的库的消费者将得到一个错误 * 在编译时 *,如果他们的代码试图调用这个方法与零或负输入?

bf1o4zei

bf1o4zei1#

在Rust中,没有办法将类型值的 * 子集 * 指定为类型。但是,您可以定义新的类型来实施规则,或者使用现有的规则,甚至有一个现有的类型用于您想要的规则:std::num::NonZeroI32

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

现在,从某种意义上说,这并没有改变任何东西--仍然需要运行时检查来构造NonZeroI32。然而,这意味着:

  • 你的函数在类型系统中而不是在文档中声明它的要求。
  • 你的功能永远不会恐慌。
  • 调用者可以选择在调用set_myfield之前进行检查,这可以简化错误处理路径。

而且,在Rust的未来版本中,可能(在适用的情况下)定义非零类型的常量,使所有内容都在编译时被检查。

use std::num::NonZeroI32;

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

(现在,这段代码无法编译,因为Option::unwrap()const评估中不可用。)

vshtjzan

vshtjzan2#

我认为你正在寻找的是类似于Zig的comptime功能的东西。
但是在Rust中,如果你试图在 * 编译时 * 防御文字0值,你可以使用macro_rules和内置的compile_error!宏。
下面是一个工作示例(Rust Playground):

struct Foo {
    myarg: i32,
}

impl Foo {
    pub fn new() -> Self {
        Self { myarg: 1 }
    }

    pub fn set_myfield(&mut self, arg: Arg) {
        self.myarg = arg.0;
    }
}

struct Arg(i32);

macro_rules! make_arg {
    (0) => {
        compile_error!("Zero is not a valid value")
    };
    ($val:expr) => {
        Arg($val)
    };
}

fn main() {
    let mut foo = Foo::new();

    // This one succeeds, as expected
    let arg = make_arg!(42);
    foo.set_myfield(arg);

    // This will fail to COMPILE with error above: "Zero is not a valid value"
    // let arg = make_arg!(0); // Will fail to compile
    // foo.set_myfield(arg);

    // However, this one succeeds
    let v = 0;
    let arg = make_arg!(v);
    foo.set_myfield(arg);
}

您可以将Arg移动到其自己的模块中,以防止用户直接设置其内部值。

相关问题