Crate ranged_integers
source ·Expand description
§Ranged integers [nightly only]
The crate provides an integer type restricted to a compile time defined range with automatic data size selection, automatic bounds calulation for arithmetics and the possibility of fixed-size array indexing and range iteration.
§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(adt_const_params, generic_const_exprs)]
// This features are for the compile-time arithmetics.
// Note that the crate sometimes causes ICEs with the feature(effects).
#![feature(const_trait_impl)]
#![feature(effects)]
§Usage and examples
§Ranged semantics
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
}
§Contents
- Data layout paradigm
- Ranged and integer primitives
- Array indexing, slicing and iteration
- Comparison
- Arithmetics
- Pattern matching
§Data layout paradigm
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 integer primitives
§Creation of 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 the methods fit()
, fit_min()
,
fit_max()
for runtime check. The Ranged
values may be compared
to each other yielding the shrinked bounds using
fit_less_than()
,
fit_less_eq()
,
fit_greater_than()
, and
fit_greater_eq()
methods.
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.fit().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.
Note: due to incomplete features limitations, the function may fail to compile with the ‘trait bound is not satisfied’ if the bounds are not explicitly specified.
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()); // this call may fail to compile
// use Ranged::<0, 6>::new instead
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 fn
s 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, slicing and iteration
The ConstInclusiveRange<MIN,MAX>
zero-size type is a range MIN..=MAX
capable to create the iterator (IntoIterator
trait implemented)
with Ranged<MIN, MAX>
output type. The r!
macro can be used instead.
The Ranged::iter_up
method creates an
iterator from the current value up to MAX
.
The arrays [T; N]
may be indexed with Ranged<0, {N-1}>
and sliced
with r!(MIN..END)
range with a reference to fixed-size array output.
let arr = [r!([1 6] 2), r!([] 3), r!([] 4), r!([] 5)];
assert_eq!(arr[r!(1..3)], [3,4]); // Slicing with array reference output
assert_eq!(arr[r!([0 3] 1)], 3); // Slicing with array reference output
// Not recommended to use this:
for i in ConstInclusiveRange::<0, 3> {
move_player(arr[i]) // iters through 0,1,2,3
}
for i in r!(0..4) {
move_player(arr[i]) // iters through 0,1,2,3
}
for i in r!(0..=3) {
move_player(arr[i]) // iters through 0,1,2,3
}
for mv in r!([1 6] 3).iter_up() {
move_player(mv) // calls with 3,4,5,6
}
§Comparison
All Eq
and Ord
operations between different Ranged types are allowed,
so as Ranged
vs integer comparisons:
assert!(r!([1 6] 4) == r!([1 10] 4));
assert!(r!([1 6] 4) != r!([1 6] 5));
assert!(r!(4) == 4);
assert!(5 != r!([1 6] 4));
assert!(r!(5) > r!([1 6] 4));
assert!(4 < r!([1 6] 5));
To constrain the output type ruled by comparison, one may use Ranged::fit_less_than
function and its siblings Ranged::fit_less_eq
, Ranged::fit_greater_than
, and Ranged::fit_greater_eq
.
§Arithmetics
The bounds of arithmetic operations results are automatically recalculated.
Currently supported:
- The basic arithmetic operations (+, -, *, /)
div_euclid()
andrem_euclid()
min()
andmax()
abs()
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);
let abs: Ranged<0,6> = r!([-1 6] -1).abs();
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>
.
§Pattern matching
A limited version is implemented with rmatch!
macro.
Macros§
- Create a ranged value or a range at compile time
- Ranged pattern matching macro
Structs§
- Const range for iterators with
Ranged
output and array indexing - A value restricted to the given bounds
Traits§
- Convert an integer value to Ranged according to its own bounds.