Как создать типизированный числовой тип с ограниченным диапазоном?
В 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 гарантирует, что только допустимые значения являются возможными значениями. Это становится очень грязным:
enum MyNumber { One, Two, ... } impl MyNumber { fn convert(i8) -> MyNumber { match { 1 => MyNumber::One, 2 => MyNumber::Two, ... } } }
Выставьте метод, который проверяет параметры перед настройкой полей, связанные с учебником функции. Это не мешает назначению с использованием конструктора структуры.
Проверяйте операнды (и принудительно исправляйте их) всякий раз, когда происходит операция. Это кажется разумным, но требует, чтобы каждый метод повторял код проверки.
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); }
Ни одно из приведенных выше решений не является очень элегантным - в некоторой степени это связано с тем, что они являются прототипными реализациями. Должен быть более чистый способ ограничения домена числового типа!
Какая комбинация типа и свойств будет проверять границы, использовать их для арифметики модульности / насыщенности и легко конвертировать в числовой примитив?
РЕДАКТИРОВАТЬ: Этот вопрос был помечен как дубликат гораздо более старого вопроса 2014 года. Я не верю, что вопросы совпадают на том основании, что Rust был пре-альфа, и в версию 1.0 были внесены значительные улучшения в язык. Разница в большем масштабе, чем между Python 2 и 3.
1 ответ
Выставьте метод, который проверяет параметры перед настройкой полей, связанные с учебником функции. Это не мешает назначению с использованием конструктора структуры.
Это делает, если поле является частным.
В 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
функция, которая теперь возвращает 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
вместо вызова именованных методов.