如何创建类型安全的范围受限数字类型?
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
}
有一些注意事项需要考虑,我尝试了不同的解决方案。我将避免对下面的示例进行泛型编程:
基于枚举类型可确保只有有效值才是可能的值。这很快变得凌乱:
enum MyNumber {
One,
Two,
...
}
impl MyNumber {
fn convert(i8) -> MyNumber {
match {
1 => MyNumber::One,
2 => MyNumber::Two,
...
}
}
}
公开一个在设置字段前检查参数的方法,教科书associated function。这不会阻止使用结构构造函数进行分配。
每当操作发生时验证操作数(并强制纠正它们)。这似乎是合理的,但需要每个方法重复验证代码。
extern crate num;
use num::Bounded;
use std::cmp;
struct MyNumber {
val: i8,
}
impl Bounded for MyNumber {
fn max_value() -> Self {
MyNumber { val: 65 }
}
fn min_value() -> Self {
MyNumber { val: -50 }
}
}
impl MyNumber {
fn clamp(&mut self) {
self.val = cmp::min(MyNumber::max_value().val,
cmp::max(MyNumber::min_value().val, self.val))
}
fn add(&mut self, mut addend: Self) {
self.clamp();
addend.clamp();
//TODO: wrap or saturate result
self.val = self.val + addend.val
}
}
fn main() {
let mut a = MyNumber { val: i8::max_value() };
let b = MyNumber { val: i8::min_value() };
a.add(b);
println!("{} + {} = {}",
MyNumber::max_value().val,
MyNumber::min_value().val,
a.val);
}
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
}
在更完整的实现中,我可能会实现 std::ops::Add
等,这样我就可以使用 a1 + a2
而不是调用命名方法。
在 Rust 中,我需要一个具有 属性 的数值类型,它的定义域围绕 0 对称。如果数字 n 是一个有效值,那么数字 -n 也必须有效。我将如何确保初始化和算术期间的类型安全?如何最好地在类型上实现模块化和饱和算法?
问题最简单的例子是:
type MyNumber = i8; // Bound to domain (-100, 100)
fn main() {
let a = MyNumber(128); // Doesn't panic when 128 > 100
}
有一些注意事项需要考虑,我尝试了不同的解决方案。我将避免对下面的示例进行泛型编程:
基于枚举类型可确保只有有效值才是可能的值。这很快变得凌乱:
enum MyNumber { One, Two, ... } impl MyNumber { fn convert(i8) -> MyNumber { match { 1 => MyNumber::One, 2 => MyNumber::Two, ... } } }
公开一个在设置字段前检查参数的方法,教科书associated function。这不会阻止使用结构构造函数进行分配。
每当操作发生时验证操作数(并强制纠正它们)。这似乎是合理的,但需要每个方法重复验证代码。
extern crate num; use num::Bounded; use std::cmp; struct MyNumber { val: i8, } impl Bounded for MyNumber { fn max_value() -> Self { MyNumber { val: 65 } } fn min_value() -> Self { MyNumber { val: -50 } } } impl MyNumber { fn clamp(&mut self) { self.val = cmp::min(MyNumber::max_value().val, cmp::max(MyNumber::min_value().val, self.val)) } fn add(&mut self, mut addend: Self) { self.clamp(); addend.clamp(); //TODO: wrap or saturate result self.val = self.val + addend.val } } fn main() { let mut a = MyNumber { val: i8::max_value() }; let b = MyNumber { val: i8::min_value() }; a.add(b); println!("{} + {} = {}", MyNumber::max_value().val, MyNumber::min_value().val, a.val); }
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
}
在更完整的实现中,我可能会实现 std::ops::Add
等,这样我就可以使用 a1 + a2
而不是调用命名方法。