Overview of Typenum
mainlibcore, it is highly portable and suitable for use on any platform.repository·main·Indexed 20 days ago
https://github.com/paholg/typenumA Rust library for type-level numbers evaluated at compile time, supporting bits, unsigned integers, and signed integers. Depending only on libcore, it provides tools for compile-time arithmetic, type-level arrays via the tarr! macro, and marker traits for converting type-level numbers to runtime values.
libcore, it is highly portable and suitable for use on any platform.Typenum re-exports its modules through the crate root. You can access any item directly via use typenum::item_name; without navigating the internal module structure.
For easier access to numeric constants, you can use a glob import from the consts module as a prelude.
// Direct import from crate root
use typenum::N2;
// Using consts as a prelude
use typenum::consts::*;The typenum crate provides type-level signed integers through three primary structures: PInt<U> for strictly positive integers, NInt<U> for strictly negative integers, and Z0 for the integer zero.
Important Note: These signed integers do not use two's complement representation. Instead, they are composed of a sign and an underlying unsigned integer. Consequently, operators that manipulate the underlying bit structure are intentionally not implemented to avoid ambiguous results.
It is recommended to use the aliases provided in the consts module rather than instantiating these structs directly.
use std::ops::{Add, Div, Mul, Rem, Sub};
use typenum::{Integer, N3, P2};
// Example of type-level arithmetic operations
assert_eq!(<N3 as Add<P2>>::Output::to_i32(), -1);
assert_eq!(<N3 as Sub<P2>>::Output::to_i32(), -5);
assert_eq!(<N3 as Mul<P2>>::Output::to_i32(), -6);
assert_eq!(<N3 as Div<P2>>::Output::to_i32(), -1);
assert_eq!(<N3 as Rem<P2>>::Output::to_i32(), -1);The typenum crate provides type-level numbers evaluated at compile time. It uses two primary categories of traits:
Marker Traits: These are used to convert a type-level number into its runtime counterpart, primarily for debugging or runtime logic. For example, the Integer trait allows you to call .to_i32() on a type.
Type Operators: These are traits that behave like functions at the type level. They perform arithmetic or logical operations where the Output associated type represents the result of the operation. For example, the Add trait can be used to compute the sum of two type-level integers.
To make type operators easier to use, the crate provides helper aliases (e.g., Sum instead of using <A as Add<B>>::Output).
use typenum::{Integer, P3, P4};
use std::ops::Add;
// Using a Type Operator directly
type X = <P3 as Add<P4>>::Output;
assert_eq!(<X as Integer>::to_i32(), 7);
// Using a helper alias
use typenum::{Sum};
type Y = Sum<P3, P4>;
assert_eq!(<Y as Integer>::to_i32(), 7);The type-level unsigned integers are built using two primary components:
UTerm: The terminating type. It represents zero and acts as the base case for recursive definitions. It always comes after the most significant bit.UInt<U, B>: A recursive definition where B is the least significant bit (Bit) and U represents the remaining more significant bits (Unsigned).Note on Uniqueness: To ensure numbers are unique, leading zeros are not allowed. For example, UInt<UTerm, B0> is forbidden.
Example Construction:
To represent the number 6 (binary 110):
U6 is UInt<UInt<UInt<UTerm, B1>, B1>, B0>use typenum::{UInt, UTerm, B0, B1};
// Represents 6
type U6 = UInt<UInt<UInt<UTerm, B1>, B1>, B0>;Type-level signed integers implement several standard operators from core::ops and typenum:
Add, Sub, Mul, Div, Rem (from core::ops).Cmp, Equal, Greater, Less (from typenum).Pow (from typenum), Neg (from core::ops), Min, Max.Arithmetic results are determined by the sign and the underlying unsigned value. For example, adding two PInt types results in a PInt, while adding a PInt and an NInt results in a type representing the difference, which could be PInt, NInt, or Z0 depending on the magnitude.
In typenum, marker traits are used to identify specific properties of type-level numbers. Unlike conventional marker traits, these often include associated constants and functions that allow you to convert the type-level number into a runtime value (e.g., i32, u64, etc.). This allows you to bridge the gap between compile-time type information and runtime logic.
use typenum::{Integer, N42};
// Accessing value via function
assert_eq!(-42, N42::to_i32());
// Accessing value via associated constant
assert_eq!(-42, N42::I32);A type-level array is represented by the TArr<V, A> struct, where V is the first element and A is the rest of the array. The array is terminated by the ATerm type.
ATerm.ATerm is U0. The length of a TArr is the length of its tail plus one.TArr can hold any type, it is specifically optimized for Integer types. Operations like addition, subtraction, and multiplication are implemented for these types.Add and Sub operations are only supported between two arrays of the same length. Adding two TArr instances results in a new TArr where each element is the sum of the corresponding elements from the input arrays.Mul), divide (Div), modulo (Rem), or negate (Neg) an entire array by a scalar value. This applies the operation to every element within the array.// Conceptual structure of a TArr
// tarr![P3, N4] expands to TArr<P3, TArr<N4, ATerm>>typenum provides type-level unsigned integers through the UInt<U, B> struct and the UTerm terminating type. Instead of using the raw structs directly, it is recommended to use the aliases provided in the consts module (e.g., U0, U1, U2, etc.).
These types support standard arithmetic and bitwise operations implemented via traits from core::ops (like Add, Sub, Mul, Div, BitAnd, BitOr, BitXor, Shl, Shr) and typenum (like Cmp, Pow).
To convert a type-level integer to a runtime value, use the to_u32(), to_u64(), to_usize(), etc., methods provided by the Unsigned trait.
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub};
use typenum::{Unsigned, U1, U2, U3, U4};
assert_eq!(<U3 as BitAnd<U2>>::Output::to_u32(), 2);
assert_eq!(<U3 as BitOr<U4>>::Output::to_u32(), 7);
assert_eq!(<U3 as BitXor<U2>>::Output::to_u32(), 1);
assert_eq!(<U3 as Shl<U1>>::Output::to_u32(), 6);
assert_eq!(<U3 as Shr<U1>>::Output::to_u32(), 1);
assert_eq!(<U3 as Add<U2>>::Output::to_u32(), 5);
assert_eq!(<U3 as Sub<U2>>::Output::to_u32(), 1);
assert_eq!(<U3 as Mul<U2>>::Output::to_u32(), 6);
assert_eq!(<U3 as Div<U2>>::Output::to_u32(), 1);
assert_eq!(<U3 as Rem<U2>>::Output::to_u32(), 1);Typenum's compile-time error messages can be difficult to parse. If you encounter complex errors, use the tnfilt tool created by Auxon to help filter and clarify them.
Project link: https://github.com/auxoncorp/tnfilt
You can perform type-level arithmetic using traits like Sum and Exp. To convert the resulting type back into a runtime value, use the Integer trait's to_i32() method (or other integer conversion methods provided by the trait).
use typenum::{Sum, Exp, Integer, N2, P3, P4};
// Addition: 3 + 4 = 7
type X = Sum<P3, P4>;
assert_eq!(<X as Integer>::to_i32(), 7);
// Exponentiation: 2^3 = 8 (Note: N2 is a signed type, so N2^3 = -8)
type Y = Exp<N2, P3>;
assert_eq!(<Y as Integer>::to_i32(), -8);The op! macro (recommended over the deprecated cmp! macro) provides a convenient way to perform type-level comparisons.
Note on syntax: If the left-hand operand is more complex than a simple identifier, you must place a comma between it and the comparison operator.
Example:
cmp!(P5 > P3) or cmp!(typenum::P5, > typenum::P3)
#[macro_use] extern crate typenum;
use typenum::consts::*
use typenum::Bit;
fn main() {
// Using the recommended op! macro inside a comparison
type Result = cmp!(P9 == op!(P1 + P2 * (P2 - N2)));
assert_eq!(Result::to_bool(), true);
}