# smallnum [![crates.io](https://img.shields.io/crates/v/smallnum.svg)](https://crates.io/crates/smallnum) [![docs.rs](https://docs.rs/smallnum/badge.svg)](https://docs.rs/smallnum/) [![GitHub Actions](https://github.com/tnballo/smallnum/workflows/test/badge.svg)](https://github.com/tnballo/smallnum/actions) [![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg)](https://github.com/tnballo/smallnum/blob/master/LICENSE) Compile-time size optimization for numeric primitives. Macros return smallest numeric type capable of fitting a static bounds. For unsigned integers, macro input is a maximum. For signed integers, macro input may be a maximum or a minimum. * Can save memory at zero runtime cost. * Embedded-friendly: `!#[no_std]`. * Safe: `#![forbid(unsafe_code)]`. ### What is this for? * **Saving memory:** aiding the compiler in memory layout optimization (aka ["struct packing"](http://www.catb.org/esr/structure-packing/)). * See zero-cost examples below. * **Improving ergonomics:** creating APIs that abstract away the underlying integer type. * E.g. method params/returns use `usize`, but stored as `u16` internally. Upcast is free. Downcast checks for loss of precision. ### Doesn't `#[repr(packed)]` already save memory? Not safely. The difference is subtle but important: * `#[repr(packed)]` removes all padding between struct fields. This incurs a performance penalty for misaligned accesses at best, and [causes undefined behavior](https://doc.rust-lang.org/nomicon/other-reprs.html#reprpacked) at worst. It's something you generally want to avoid. * `smallnum` aids packing while maintaining the target's native alignment, without removing padding. It can actually improve [data cache] performance while being fully safe. For extreme size optimization, you're free to combine `smallnum` with `#[repr(packed)]`. ### Example: Collection Index When the size of a collection is known at compile-time, the variable used to index it can be size-optimized. * **Target:** Value for collection/container index operator * **Yield:** `x * 1` where: * `x < size_of()` ```rust use smallnum::{small_unsigned, SmallUnsigned}; use core::mem::size_of_val; const MAX_SIZE: usize = 500; let mut my_array: [u8; MAX_SIZE] = [0x00; MAX_SIZE]; let idx: usize = 5; let small_idx: small_unsigned!(MAX_SIZE) = 5; // Equivalent values my_array[idx] = 0xff; assert_eq!(my_array[idx], my_array[small_idx.usize()]); // Memory savings (6 bytes on a 64-bit system) #[cfg(target_pointer_width = "64")] assert_eq!(size_of_val(&idx) - size_of_val(&small_idx), 6); ``` Notice that having the trait `SmallUnsigned` in scope allows `small_idx.usize()` to be called. This function returns a `usize` for convenient indexing, regardless of which type the macro selected (`u16` in the above example, hence the 6 byte savings over a 64-bit host's `u64`). ### Example: Tree Node Metadata When the maximum capacity of a tree is known at compile time, metadata stored in every node can be size-optimized. * **Target:** Internal metatdata * **Yield:** `x * n` where: * `x <= size_of()` * `n == node_cnt` ```rust use smallnum::small_unsigned; use core::mem::size_of; const MAX_CAPACITY: usize = 50_000; // Regular node in a binary tree pub struct BinTree { value: T, left_child: Option>>, right_child: Option>>, subtree_size: usize, } // Node with size-optimized metadata pub struct SmallBinTree { value: T, left_child: Option>>, right_child: Option>>, subtree_size: small_unsigned!(MAX_CAPACITY), } // Per-node memory savings (8 bytes on a 64-bit system) #[cfg(target_pointer_width = "64")] assert_eq!(size_of::>() - size_of::>(), 8); ``` ### Example: Index-based Graphs When implementing an [{index,arena}-based graph](http://smallcultfollowing.com/babysteps/blog/2015/04/06/modeling-graphs-in-rust-using-vector-indices/) whose maximum capacity is known at compile-time, indexes stored in every structure (edge or node) can be size-optimized. * **Target:** Internal "pointer" representation * **Yield:** `(x + y) * n` where: * `x <= size_of()` * `y <= size_of>()` * `n == edge_cnt` ```rust use smallnum::small_unsigned; use core::mem::size_of; const MAX_CAPACITY: usize = 50_000; // Based on "Modeling graphs in Rust using vector indices" by Niko Matsakis (April 2015) // http://smallcultfollowing.com/babysteps/blog/2015/04/06/modeling-graphs-in-rust-using-vector-indices/ // Unoptimized indexes pub type NodeIdx = usize; pub type EdgeIdx = usize; pub struct EdgeData { target: NodeIdx, next_outgoing_edge: Option } // Optimized indexes pub type SmallNodeIdx = small_unsigned!(MAX_CAPACITY); pub type SmallEdgeIdx = small_unsigned!(MAX_CAPACITY); pub struct SmallEdgeData { target: SmallNodeIdx, next_outgoing_edge: Option } // Per-edge memory savings (18 bytes on a 64-bit system) #[cfg(target_pointer_width = "64")] assert_eq!(size_of::() - size_of::(), 18); ``` ### Advanced Examples See [`examples/`](https://github.com/tnballo/smallnum/tree/master/examples) directory, `cargo run --example `. ### Macro <-> Type Selection Set * [`small_unsigned!`](crate::small_unsigned) <-> (`u8`, `u16`, `u32`, `u64`, `u128`) * [`small_signed!`](crate::small_signed) <-> (`i8`, `i16`, `i32`, `i64`, `i128`) ### License and Contributing Licensed under the [MIT license](https://github.com/tnballo/smallnum/blob/master/LICENSE). Contributions are welcome!