In Rust, I have need of a numeric type with the property of having a domain symmetric around 0. If a number n is a valid value, then the number -n must also be valid. How would I ensure type-safety during initialization and arithmetic? How would it be best to implement modular and saturation arithmetic on the type?
The simplest example of the problem is:
type MyNumber = i8; // Bound to domain (-100, 100)
fn main() {
let a = MyNumber(128); // Doesn't panic when 128 > 100
}
There are a few considerations to make, and I've attempted different solutions. I'll avoid generic programming for the examples of them below:
Basing the type off enum ensures that only valid values are possible values. This becomes messy very fast:
enum MyNumber { One, Two, ... } impl MyNumber { fn convert(i8) -> MyNumber { match { 1 => MyNumber::One, 2 => MyNumber::Two, ... } } }
Expose a method which checks parameters before setting the fields, the textbook associated function. This doesn't prevent assigning using the struct constructor.
Validate operands (and forcibly rectify them) whenever an operation occurs. This seems reasonable, but requires each method to repeat the validation code.
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 of the solutions above are very elegant - to some degree this is because they are prototype implementations. There must be a cleaner way to limit the domain of a numeric type!
What combination of type and traits would check bounds, use them for modular/saturation arithmetic, and easily convert to a numeric primitive?
EDIT: This question has been flagged as a duplicate of a much older question from 2014. I do not believe the questions are the same on the grounds that Rust was pre alpha and major improvements to the language were brought with version 1.0. The difference is of a greater scale than that between Python 2 and 3.
It does if the field is private.
In Rust, functions in the same module, or submodules, can see private items... but if you put the type into its own module, the private fields are not available from outside:
And here's a simple
impl
with a saturating add, which leveragesi8
's built insaturating_add
to avoid wrapping so that simple clamping works. The type can be constructed using thepub fn new
function, which now returns anOption<MyNumber>
since it can fail.Other modules can
use
the type:And some example uses:
Playground
In a more complete implementation I would probably implement
std::ops::Add
etc. so that I could usea1 + a2
instead of calling named methods.