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. - Dynamic library loading is not guaranteed to return the same address for
generic_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 reserve the storage .comm section with the name of unique function + gen_static_ prefix, and after that
we compute the PC-relative address using lea on x86, adrp on arm, and so on.
Since .comm section stays the same across binary per each T we can safely inline generic_static calls and guarantee 1 or 2 instruction
access to said generics.
§Platform support
- x86_64
- aarch64
- x86_32
- arm (Thumb as well)
- riscv32/riscv64
- loongarc32/loongarch64
- powerpc/powerpc64
- s390x
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
- 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.