Crate ranged_integers[−][src]
Expand description
Ranged integers [nightly only]
The crate provides an integer type restricted to a compile time defined range with automatic size selection and automatic bounds calulation for arithmetics.
Prerequisites
The library usage requires the following Rust features enabled in the user crate or application:
// Without this rustc generates errors and sometimes panics. #![feature(const_generics, const_evaluatable_checked)]
Usage and examples
Integer size
The Ranged automatically chooses the smallest size possible according
to MIN..=MAX range.
It supports i8, u8, i16, u16, i32, u32, i64, u64 and i128 layouts (u128 is not supported),
and a special zero-size layout for “constant” values with MIN==MAX.
use core::mem::size_of; assert_eq!(size_of::< Ranged<42, 42> >(), 0); // It's always 42, no need to store it assert_eq!(size_of::< Ranged<-1, 127> >(), 1); // Fits i8 assert_eq!(size_of::< Ranged<0, 200> >(), 1); // Fits u8 assert_eq!(size_of::< Ranged<-1, 200> >(), 2); // Fits i16, doesn't fit i8 or u8 assert_eq!(size_of::< Ranged<0, 90000> >(), 4); // The range fits i32
The implementation heavily relies on the optimizer.
Ranged and primitive interaction
Use Ranged<MIN, MAX> type to be sure of the value range:
fn move_player(dice_roll: Ranged<1, 6>) { let x : i32 = dice_roll.into(); // Conversion is allowed, i32 can store 1..=6 }
Create Ranged at compile time
The Ranged::create_const can be used to create a
Ranged value checking it at compile time.
The macro r! does the same but a bit shorter.
// Way 1: specify the bounds explicitly move_player(Ranged::<1,6>::create_const::<4>()); move_player(r!([1 6] 4)); // Same thing // Way 2: do not specify the bounds when possible move_player(Ranged::create_const::<4>()); move_player(r!([] 4)); // Same thing let x: Ranged::<0, 100> = Ranged::create_const::<42>(); let y: Ranged::<0, 100> = r!([] 42); // Same thing // Way 3: a special case with the single possible value let x = Ranged::<4, 4>::create_const::<4>(); let y = r!(4); // Same thing
It fails if the bounds are corrupted:
move_player(r!([] 7)); // Error: Can't store 7 in [1 6] inverval
move_player(r!([1 7] 7)); // Error: type mismatch, move_player() requires Ranged<1, 6>
Ranged -> Ranged conversion
The Ranged can be converted to the type with different bounds using
expand() generic method (compile-time check)
and try_expand()->Option (runtime check).
let expandable: Ranged<4, 5> = r!([] 5); // Fits Ranged<1,6> accepted by move_player let overlapping: Ranged<4, 9> = r!([] 5); // Doesn't fit, but the value 5 is acceptable move_player(expandable.expand()); move_player(overlapping.try_expand().unwrap());
Shrinking with expand() is forbidden:
move_player(overlapping.expand()); // Error: the bounds 4..=9 can't fit in 1..=6
int -> Ranged conversion
Way 1: ensure the bounds with Ranged::new(i128) -> Option<Ranged> function
let some_int = 4; let some_wrong_int = 8; assert!(Ranged::<0, 6>::new(some_int) == Some(r!([0 6] 4))); assert!(Ranged::<0, 6>::new(some_wrong_int) == None); move_player(Ranged::new(some_int).unwrap());
Way 2: use the Remainder operation with the “const” divisor
let x: Ranged<-9, 9> = 15_i32 % r!(10); let y: Ranged<0, 9> = 15_u32 % r!(10); assert!(x == r!(5)); assert!(y == r!(5));
Way 3: Convert the primitive types to Ranged with their native bounds using AsRanged
use ranged_integers::AsRanged; let x = 15_u8.as_ranged(); // Ranged<0, 255> let y = 15_i16.as_ranged(); // Ranged<-32768, 32767>
Ranged -> int conversion
int::From trait is implemented when the value is proved to
fit into the result type:
let x = r!([0 200] 20); let y: u8 = x.into(); // 0..=200 fits u8
let x = r!([0 200] 20); let y: i8 = x.into(); // 0..=200 doesn't fit i8
From and Into operations can’t be used in const context.
A set of const fns allows const conversions to
any integer primitive except for u128:
let x = r!([0 200] 20); let y = x.u8(); // y is u8 let z = x.i16(); // z is i16 let w = x.usize(); // w is usize
let x = r!([0 200] 20); let err = x.i8(); // Error: 0..=200 doesn't fit i8
Array indexing
The arrays [T; N] may be indexed with Ranged<0, {N-1}>:
let arr = [10, 11, 12, 13, 14]; let idx = r!([0 4] 2); assert_eq!(arr[idx], 12);
Comparison
Equality and inequality operations between different Ranged types are allowed:
assert!(r!([1 6] 4) == r!([1 10] 4)); assert!(r!([1 6] 4) != r!([1 6] 5));
Arithmetics
The basic arithmetic operations, min() and max() functions are implemented. The bounds of values are automatically recalculated:
let x = r!([1 6] 5); let y = r!([1 6] 4); let a = x + y; // The minimum is (1+1)=2, the maximum is (6+6)=12 let check_add: Ranged<2, 12> = a; // Range assertion assignment assert_eq!(check_add, r!(9)); let s = x - y; // The minimum is (1-6)=-5, the maximum is (6-1)=5 let check_sub: Ranged<-5, 5> = s; // Range assertion assignment assert_eq!(check_sub, r!(1)); let m = x * y; // The minimum is (1*1)=1, the maximum is (6*6)=36 let check_mul: Ranged<1, 36> = m; // Range assertion assignment assert_eq!(check_mul, r!(20)); let d = x / y; // The minimum is (1/6)=0, the maximum is (6/1)=6 let check_div: Ranged<0, 6> = d; // Range assertion assignment assert_eq!(check_div, r!(1)); let r = x % y; let check_rem: Ranged<0, 5> = r; // Range assertion assignment assert_eq!(check_rem, r!(1)); let n = -x; let check_neg: Ranged<-6, -1> = n; // Range assertion assignment assert_eq!(check_neg, r!(-5)); let min: Ranged<1,6> = x.min(a); let max: Ranged<2,12> = x.max(a);
The division and remainder are allowed only if it’s impossible to store “0” in the divisor:
let x = r!([1 6] 4); let y = r!([0 6] 5); let z = r!([-1 6] 5); let d = x / y; // Error: y can be 0 let e = x % z; // Error: z can be 0
The true bounds calculation routine for Rem operation is far too complex.
In this library the calculated bounds will never exceed 1-DMAXABS..=DMAXABS-1 where DMAXABS is the
maximum of the divisor absolute value.
This kind of Rem followed by expand is available for any dividend:
let x = r!([-1000 1000] 500); let y = r!([-1 1000] 500); let d = r!([1 10] 7); let r: Ranged<-9, 9> = (x%d).expand(); // In this case, it expands just from Ranged<-9, 9> to itself let r: Ranged<-9, 9> = (y%d).expand(); // In this case, it expands from Ranged<-1, 9>
But the actual calculation routine can produce smaller bounds:
// In the general case the output is Ranged<1-MAX, MAX-1>, MAX from divisor (by absolute value) let x: Ranged<-9, 9> = (r!([-1000 1000] 500) % r!([1 10] 7)); let x: Ranged<-9, 9> = (r!([-1000 1000] 500) % r!([-10 -1] -7)); // If the dividend is nonnegative or nonpositive, // the output range is limited to 0. let x: Ranged<0, 9> = r!([0 100] 15) % r!(10); let x: Ranged<-9, 0> = r!([-100 0] -15) % r!(10); // The limit can't exceed the dividend's MIN(if negative) or MAX(if positive): let x: Ranged<-10, 10> = r!([-10 10] 4) % r!([1 1000] 70); // If the divisor is "constant", the output bounds are the true bounds: let x: Ranged<4, 7> = r!([14 17] 15) % r!(10); // In particular, if both operands are "constant", the result is "constant" let x: Ranged<5, 5> = r!(15) % r!(10);
Following these rules, the calculated bounds may be wider than the true ones, like
Ranged<36289, 36292> % Ranged<6, 9> = Ranged<0, 8> while the
result never exceeds Ranged<1, 4>.
Macros
Create a ranged value at compile time
Structs
A value restricted to the given bounds
Traits
Convert an integer value to Ranged according to its own bounds.
Functions
Create a range iterator with Ranged output