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 calculation for arithmetics and the possibility of fixed-size array indexing and range iteration.
The library relies to the unstable const generics-related features.
It causes ICEs on some Rust toolchains and may once stop working.
It will require nightly Rust until the generic-const-exprs feature is stabilized.
Consider the other stable ranged integers crates, allowing to ensure the bounds and
perform the arithmetic operations withing the type:
- deranged. It provides checked/saturating and unsafe unchecked arithmetics within the specified bounds, equalities and comparisons. There is a procedural macro to select a type of ranged based on literal bounds.
- light_ranged_integers provides a nice way to choose the type of arithmetic operation (wrapping/saturating/panicking) with the type-level mark.
§Version info
The version 0.10.1 was built for nightly-2025-06-24 toolchain.
The const arithmetics were added. The const fn-s are used for compile-time arithmetics, since
the std::opts traits are mostly non-const. The i128 representation was removed from Ranged.
This has been done because the bounds violation generates an unpleasant error in which the
place of occurrence is not specified, only the violated line inside ranged_integers crate. So,
the possibility to violate the i128 bounds during the automatic bounds calculation is eliminated.
The bounds of i64/u64 are explicitly written as a trait constraint, so it generates a
much more helpful error message.
§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)]Note that the features needed depend on the package version.
§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>) { // Ranged<1, 6> is automatically of 1 byte
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 and u64 layouts (i128 and u128 are 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 i32The 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 thingIt fails if the bounds are corrupted:
move_player(r!([] 7)); // Error: Can't store 7 in [1 6] intervalmove_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 shrunk 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 insteadWay 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 u8let x = r!([0 200] 20);
let y: i8 = x.into(); // 0..=200 doesn't fit i8From 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 usizelet 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 (+, -, *, /), not available in const context
- The const arithmetic functions (
add,sub,mul,div) div_euclidandrem_euclidminandmaxabsandneg
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();
let neg: Ranged<-6,1> = r!([-1 6] -1).neg();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 0The 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 from one side.
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.
Modules§
- value_
check - The compile-time infrastructure for Ranged.
Macros§
Structs§
- Const
Inclusive Range - Const range for iterators with
Rangedoutput and array indexing - Ranged
- A value restricted to the given bounds
Traits§
- AsRanged
- Convert an integer value to Ranged according to its own bounds.