你如何在 Rust 中指定值约束?

How do you specify value constraints in Rust?

我正在寻找一种将类型约束移动到某种包装器中的方法。例如,在 Ada 中,您可能会看到类似这样的内容:

type Element is Integer range 100 .. 1000;

它正在定义一个新类型 Element --- 虽然仍然是一个整数 --- 被绑定到一个特定的范围。还有 mod 会循环(超级有用)。

在 Rust 中,到目前为止,我一直在我的所有函数中手动检查它,即:

if x < 100 || x >= 1000 {
    // throw some kind of error
}

但是如果定义一个 new 类型来为我在赋值时执行此检查,这将是非常好的,类似于默认情况下整数不能溢出的方式。我知道我们没有继承,但也许我可以实现某种 trait

TL;DR: 我确定我的方法不是最佳实践,但标准替代方法是什么?

一种可能是这样Playground

我添加了这个宏 add_constraint! 来简化添加约束的语法。

但是请注意,使用当前方法,每种类型只能有一个约束

trait ValueConstraint {
    type MaybeError;
    fn validate(&self) -> Self::MaybeError;
}

macro_rules! add_constraint {
    ($implementor:ty, $predicate:expr) => { 
        impl ValueConstraint for $implementor {
            type MaybeError = Option<()>;
            fn validate(&self) -> Self::MaybeError {
                if $predicate(self) {
                    Some(())
                } else {
                    None
                }
            }
        }
    }
}

add_constraint!(i32, |&x| x >= 100 && x < 1000);


fn work_with_number(mut x: i32) -> Option<i32> {
    x.validate()?;
    x += 42;
    x.validate()?;
    Some(x)
}

fn main() {
    println!("{:?}", work_with_number(100)); // Some(142)
    println!("{:?}", work_with_number(999)); // None
}

But it would be really nice to instead define a new type that performs this check for me on assignment, similar to how integers can't overflow by default.

这确实是最好的。

您可以将其定义为用户类型:

struct BoundedU16<const MIN: u16, const MAX: u16>(u16);

然后定义所有你期望的方法,从新的开始:

impl<const MIN: u16, const MAX: u16> BoundedU16<MIN, MAX> {
    pub const fn new(value: u16) -> Result<Self, BoundError> {
        if value >= MIN && value <= MAX {
            Ok(Self(value))
        } else {
            Err(BoundError(value, MIN, MAX))
        }
    }
}

主要缺点是目前您无法拥有 BoundedInteger<T, const MIN: T, const MAX: T>。解决方法是使用宏来定义多个 Bounded[I|U][8|16|32|64|size].

然后可以声明type Element = BoundedU16<100, 1000>;.

但请注意任何 Element 在这里只是一个别名,而不是新类型。如果你用 struct Element(BoundedU16<100, 1000>) 声明一个新的 type 那么你需要再次实现(或派生)所有特征。


特征允许您添加 validate 方法,但不允许您实现 AddSub、...自动 validate。这是一个较差的解决方案。

您可以将您的数字类型包装到一个可以对范围进行边界检查的结构中。

use std::ops::Range;

#[derive(Debug)]
struct Bounded<T: PartialOrd> {
    val: T,
    range: Range<T>
}

impl<T: PartialOrd> Bounded<T> {
    fn try_new(val: T, range: Range<T>) -> Option<Self> {
        if range.contains(&val) {
            return Some(Bounded{val, range});
        }
        else {
            return None;
        }
    }
}

fn main() {
    let a = Bounded::try_new(4u32, 0..5);
    println!("{:?}", a);
    
    let b = Bounded::try_new(6u32, 0..5);
    println!("{:?}", b);
}

给我们

Some(Bounded { val: 4, range: 0..5 })
None

您可以选择 return Result<Self>