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

问题描述 投票:0回答:2

很简单,我在一个结构上有一个 setter 方法,它设置一个类型为

i32
的字段值,我不想允许零或负数。我用这样的
assert!
宏实现了这一点:

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

如果调用者调用set_myfield(0),这似乎会在运行时

引起恐慌
,但在编译时没有给出错误。是否有任何类型的断言我可以编写,以便如果我的库的使用者的代码尝试使用零或负输入调用此方法,我的库的使用者将在编译时收到错误

rust assert compile-time
2个回答
1
投票

我认为您正在寻找的是Zig的

comptime
功能。

但是在 Rust 中,如果你试图在

compile time
防御文字 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
移动到它自己的模块,以防止用户直接设置它的内部值。


0
投票

在 Rust 中,无法将类型值的 subset 指定为类型。但是,您可以定义强制执行规则的新类型,或使用现有类型,甚至还有一个现有类型用于您想要的规则:

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
评估中不可用。)

© www.soinside.com 2019 - 2024. All rights reserved.