如何创建类型安全的范围受限数字类型?

How to create a typesafe range-limited numeric type?

在 Rust 中,我需要一个具有 属性 的数值类型,它的定义域围绕 0 对称。如果数字 n 是一个有效值,那么数字 -n 也必须有效。我将如何确保初始化和算术期间的类型安全?如何最好地在类型上实现模块化和饱和算法?


问题最简单的例子是:

type MyNumber = i8; // Bound to domain (-100, 100)

fn main() {
    let a = MyNumber(128); // Doesn't panic when 128 > 100
}

有一些注意事项需要考虑,我尝试了不同的解决方案。我将避免对下面的示例进行泛型编程:

None 上面的解决方案非常优雅——在某种程度上这是因为它们是原型实现。必须有一种更简洁的方法来限制数字类型的域!

什么类型和特征的组合会检查边界,将它们用于 modular/saturation 算术,并轻松转换为数字基元?

编辑:这个问题已被标记为 2014. I do not believe the questions are the same on the grounds that Rust was pre alpha 中一个更老的问题的重复,并且 1.0 版对语言进行了重大改进。差异比 Python 2 和 3 之间的差异更大。

Expose a method which checks parameters before setting the fields, the textbook associated function. This doesn't prevent assigning using the struct constructor.

如果字段是私有的,它会这样做。

在 Rust 中,同一模块或子模块中的函数可以看到私有项...但是如果将类型放入其自己的模块中,则私有字段无法从外部获得:

mod mynumber {
    // The struct is public, but the fields are not.
    // Note I've used a tuple struct, since this is a shallow
    // wrapper around the underlying type.
    // Implementing Copy since it should be freely copied,
    // Clone as required by Copy, and Debug for convenience.
    #[derive(Clone,Copy,Debug)]
    pub struct MyNumber(i8);

这里是一个带有饱和添加的简单 impl,它利用 i8 的内置 saturating_add 来避免环绕,以便简单的钳位工作。该类型可以使用 pub fn new 函数构造,现在 returns 一个 Option<MyNumber> 因为它可能会失败。

    impl MyNumber {
        fn is_in_range(val: i8) -> bool {
            val >= -100 && val <= 100
        }
        fn clamp(val: i8) -> i8 {
            if val < -100 {
                return -100;
            }
            if val > 100 {
                return 100;
            }
            // Otherwise return val itself
            val
        }
        pub fn new(val: i8) -> Option<MyNumber> {
            if MyNumber::is_in_range(val) {
                Some(MyNumber(val))
            } else {
                None
            }
        }

        pub fn add(&self, other: MyNumber) -> MyNumber {
            MyNumber(MyNumber::clamp(self.0.saturating_add(other.0)))
        }
    }
}

其他模块可以use类型:

use mynumber::MyNumber;

一些示例使用:

fn main() {
    let a1 = MyNumber::new(80).unwrap();
    let a2 = MyNumber::new(70).unwrap();
    println!("Sum: {:?}", a1.add(a2));
    // let bad = MyNumber(123); // won't compile; accessing private field
    let bad_runtime = MyNumber::new(123).unwrap();  // panics
}

Playground

在更完整的实现中,我可能会实现 std::ops::Add 等,这样我就可以使用 a1 + a2 而不是调用命名方法。