很简单,我在一个结构上有一个 setter 方法,它设置一个类型为
i32
的字段值,我不想允许零或负数。我用这样的assert!
宏实现了这一点:
pub fn set_myfield(&mut self, arg: i32) {
assert!(arg > 0);
self.myfield = arg;
}
如果调用者调用set_myfield(0)
,这似乎会在运行时
引起恐慌,但在编译时没有给出错误。是否有任何类型的断言我可以编写,以便如果我的库的使用者的代码尝试使用零或负输入调用此方法,我的库的使用者将在编译时收到错误?
我认为您正在寻找的是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
移动到它自己的模块,以防止用户直接设置它的内部值。
在 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
评估中不可用。)