I would like to put together an RFC in the near future about encapsulating the integer set within the type system. Those familiar with the typenum
crate can probably skim-through the next part.
The current standardised way of using number tools within the type system, is to use const generics. This is highly valuable when you need to specify a value available for use at runtime, but compile time defined. Case in point, when using arrays, and need to be generic with respect to its size, but monomorphised to a specific length: const LEN: usize
.
This language feature has its limitations, and the many nightly features that address various aspects of its limitations act as one way of illustrating this.
Here is my current draft thesis statement:
If you abandon the notion of an integer being a primitive value, and replace that thinking with an integer being a marker type that implements traits, it opens the door to exciting possibilities.
For example, consider a scenario where you want to constrain a method in terms of numbers, such that A < 6 and > B, and that B > 2, but also you want to multiply that, and round up to the next multiple of 8. This may have appear contrived, but it's coming from actual needs. Thinking of the needed math in terms of values, it might look like this
fn x<A: < 6 && > B, B: >2>() -> (A * B + 7) / 8 {
todo!()
}
The solution to this, at present, is to use typenum
using the typenum crate, this would look a little like this:
fn x<A, B>()
where
A: Unsigned + IsGreater<B, Output = True> + Mul<B> + IsLess<U6, Output = True>,
B: Unsigned + IsGreater<U2, Output = True>,
op!(A * B): Add<U7>,
op!(A * B + U7): Div<U8>,
-> op!((A * B + U7) / U8) {
todo!()
}
To whit:
- A can be any type,but it must be a natural number that implements the
< 6
trait such that it's true - it also must implement the
* B
trait, and that traits associated typeOutput
must also implement the+ 7
trait. - the
+ 7
imple must have an output which itself impls the/ 8
trait - the return type is the output of that
/ 8
trait - B must implement the natural number trait
- B must also implement the
> 2
trait with the output being true
This is all done without having to think of machine representation (usize
or any of the uN
s), and works since stable 1.37.0, and looks like it would work with first stable 2018 edition (1.31.0). Compiler gives 2 errors: Self
constructors (issue #51994), and passing an extern crate without --extern
(issue #53130) .
Why not just use typenum as a dependency?
This crate is particularly novel, in that it's sole focus is to implement a language design feature. It has more in common with GATs, const generics and compile time utilities that work with types (such as orphan rule checker), than it does with code that lis in the nursery. I believe that in order to fully realise the potential value that this approach can bring to the language, the compiler itself would need to see numbers from this "a set of types that implements traits" perspective directly.
- using
typenum::U7
when the constraint is< 6
spits out an unintelligible error. Tools exist to address this, but solutions should ideally be made upstream. - though an extremely clever implementation of functional programming type theory, the inherent nature of it puts a lot of weight on the compiler. Integrating it into the type system would allow the source of the this burden to be bypassed.
- bridging between types and const generics is non trivial in many respects. Typenum often eases this bridging, and with nightly features, this accounts for many use cases. Numbers as types baked into the language is likely to make the level of complexity purely a function of the complexity of the solution, and be eased, rather than exacerbated, by the language and its standard tooling.
Before I publish a formal RFC, I would like to hear out what people have to say on this. I'm particularly interested in hearing from those that have some involvement in the work on const generics, GATs and other initiatives that endeavour to improve the accessibility and utility of cool type wizardry.