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