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

8

我希望找到一种方法将类型约束移入某种包装器中。以Ada为例,您可能会看到类似于这样的代码:

type Element is Integer range 100 .. 1000;

定义了一个新类型Element,它仍然是整数类型,但受到特定范围的限制。此外还有一种循环计算的方法mod(非常有用)。

在Rust中,我一直在手动检查所有函数中是否符合这个条件,例如:

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

然而,定义一个新的类型,在赋值时为我执行这个检查会更好,类似于整数默认情况下不会溢出。我知道我们没有继承,但也许有一些trait可以实现?

简而言之:我知道我的方法不是最佳实践,但标准替代方案是什么?

3个回答

6

但是如果可以定义一个新类型,在赋值时自动执行此检查,就会更好,类似于整数默认情况下不会溢出。

确实如此。

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

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

然后定义你所期望的所有方法,以new:开头:

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>)声明新的类型,则需要再次实现(或派生)所有特性。
特性允许您添加validate方法,但不允许您实现自动validateAddSub等操作。这是一个较差的解决方案。

0

一种可能性就是像这样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
}

0
你可以将你的数字类型封装到一个结构体中,以便在范围上进行边界检查。
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

如果需要的话,您可以返回一个Result<Self>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接