static-generics
Zero-cost generic statics for Rust.
Based on the ideas of cynecx/generic-statics,
and implemented to be zero-cost*: one–two instructions to access a generic
(lea, adrp+add, auipc+addi, larl, …) instead of a call to the
accessor function.
*Zero cost applies only to supported platforms, unsupported platforms go through fallback of HashMap + Mutex and require std feature to be enabled.
use ;
use ;
define_namespace!;
let hits = ;
hits.fetch_add;
Why
Rust has no generic statics unlike C++, and default solution to that is using typemap/HashMap<TypeId, ...> which
require locking, allocation, hashing on each access which is expensive and slow. static-generics implements zero-cost
solution which allows to implement generic statics in ~1-2 instructions on supported platforms (and falls back to hashmap on unsupported).
Requirements and limits
- Only
bytemuck::Zeroabletypes can be stored in the static generic (use OnceSlot to lazily initialize any other types in static generic). - No
Dropsupport, all static generics are essentially leaked. If you need to clean-up memory after them, it has to be done manually. - Within a single crate the same
(Namespace, T)is always the same address. Across crates or dynamic libraries sharing is not guaranteed: depending on optimization and linking, another crate may alias your slot or keep its own copy. Dynamic library loading in particular is not guaranteed to return the same address forgeneric_static::<i32>()done inlibA.soandlibB.so.
Implementation
We use inline assembly + monomorphization of rustc to emit "unique" function body per each T. In the assembly
code block we define storage with weak + COMDAT linkage in a per-symbol .bss section with the name of unique function
gen_static_prefix, and after that we compute the PC-relative address usingleaon x86,adrpon arm, and so on.
Since the storage symbol is linkonce, the linker keeps a single copy per binary, so inlined generic_static
calls stay at 1 or 2 instructions with no runtime overhead.
Platform support
- x86_64
- aarch64
- x86_32
- arm (Thumb as well)
- riscv32/riscv64
- loongarc32/loongarch64
- powerpc/powerpc64
- s390x
- wasm32: ONLY with
nightlyfeature enabled, stable Rust does not supportasm!()on WASM.
On other platforms (or with Miri/Cranelift detected) the crate will fallback to slow implementation via HashMap and Mutex.