2017-01-08 6 views
4

В 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 гарантирует, что возможны только допустимые значения. Это становится грязно очень быстро:

    enum MyNumber { 
        One, 
        Two, 
        ... 
    } 
    impl MyNumber { 
        fn convert(i8) -> MyNumber { 
         match { 
          1 => MyNumber::One, 
          2 => MyNumber::Two, 
          ... 
         } 
        } 
    } 
    
  • Expose метод, который проверяет параметры перед установкой полей, учебник associated function. Это не препятствует назначению с помощью конструктора struct.

  • Проверять операнды (и принудительно исправлять их) всякий раз, когда происходит операция. Это кажется разумным, но требует, чтобы каждый метод повторял код проверки.

    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); 
    } 
    

Ни одно из решений выше, не очень элегантно - в какой-то степени это происходит потому, что они являются реализациями прототипа. Должен быть более чистый способ ограничить область числового типа!

Какая комбинация типов и признаков будет проверять границы, использовать их для модульной/насыщенной арифметики и легко конвертировать в числовой примитив?

EDIT: Этот вопрос был помечен как дубликат гораздо более старого вопроса от 2014. Я не верю, что вопросы одинаковы на том основании, что Rust был pre alpha, и основные улучшения в языке были приведены с версией 1.0. Отличие состоит в больший масштаб, чем между Python 2 и 3.

+0

Даже если * что-то достаточно важное было изменено или добавлено в Rust, все равно было бы целесообразно добавить эти ответы на существующий вопрос. В противном случае каждый отдельный вопрос о переполнении стека должен быть повторно запрошен каждые несколько месяцев, если что-то изменилось с окружающей средой. Полученный вами ответ повторяет уже существующий ответ (вплоть до реализаций конструктора и признаков), поэтому я не видел причин, по которым они не дублируются. Есть также проспект щедрости, если вы считаете, что более старый вопрос требует большего внимания. – Shepmaster

+0

@Shepmaster Благодарим вас за разъяснение. Это справедливая точка зрения по этому вопросу. – Aaron3468

+0

Не беспокойтесь! Более широкое сообщество может по-прежнему не соглашаться со мной и голосовать за повторное открытие; Я просто пытаюсь внести свой вклад в поддержание чистоты.^_^ – Shepmaster

ответ

6

Разоблачи метод, который проверяет параметры перед установкой полей, соответствующая функции учебника. Это не мешает назначению с помощью конструктора struct .

Он делает это, если поле является закрытым.

В Русте, функции в том же модуле, или подмодуль, можно увидеть личные вещи ... но если вы поставите тип в свой собственный модуль, частные поля не доступны снаружи:

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 
} 

Playground

В более полной реализации, я бы, вероятно, реализовать std::ops::Add и т.д. так что я мог бы использовать a1 + a2 вместо вызова названных методов.