Expand description
§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 static_generics::{define_namespace, namespace::NamespaceExt};
use core::sync::atomic::{AtomicU64, Ordering};
define_namespace!(Counters);
let hits = Counters::get::<AtomicU64>();
hits.fetch_add(1, Ordering::Relaxed);§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.
Re-exports§
pub use namespace::Namespace;pub use namespace::NamespaceExt;pub use once::OnceSlot;
Modules§
- namespace
- Namespaced generic statics with zero-cost access.
- once
- Lazy, one-time initialization on top of
generic_static.
Macros§
- define_
namespace - Define a namespace for static generics. Namespace is like “scope” for generics. Were you to always use
DefaultNamespaceyou would end up with eventually running out of static slots. Defining multiple namespaces allows you to have virtually unlimited set of static generics per each usage. - define_
static - Declare a typed accessor function for one generic static.
Structs§
Traits§
- Pod
- Marker trait for “plain old data”.
- Zeroable
- Trait for types that can be safely created with
zeroed.
Functions§
- get
- Generic static in the
DefaultNamespace. - get_
const - Generic const-keyed static in the
DefaultNamespace. - get_
const_ ⚠mut - Raw (
*mut T) const-keyed view in theDefaultNamespace. - get_in
- Generic static in an explicit namespace.
- get_
in_ const - Generic const-keyed static in an explicit namespace.
- get_
in_ ⚠const_ mut - Raw (
*mut T) const-keyed view in an explicit namespace. - get_
in_ ⚠mut - Raw (
*mut T) view of the generic static in an explicit namespace. - get_mut⚠
- Raw (
*mut T) view of the generic static in theDefaultNamespace. - once
- Lazily-initialized generic static in the
DefaultNamespace. - once_
const - Lazily-initialized const-keyed generic static in the
DefaultNamespace.