static_generics/namespace.rs
1//! Namespaced generic statics with zero-cost access.
2//!
3//! Each `(Namespace, T)` slot is emitted the way clang emits a C++ template
4//! static: a weak, COMDAT-grouped, zero-initialized definition in its own
5//! `.bss` section. The address is then computed with the optimal
6//! PC-relative sequence for the architecture, so access compiles to 1-2
7//! instructions with no runtime overhead on supported targets.
8//!
9//! ```rust
10//! use core::sync::atomic::{AtomicU64, Ordering};
11//! use static_generics::{define_namespace, namespace::Namespace};
12//!
13//! define_namespace!(MyNs);
14//!
15//! MyNs::generic_static::<AtomicU64>().store(7, Ordering::Relaxed);
16//! assert_eq!(MyNs::generic_static::<AtomicU64>().load(Ordering::Relaxed), 7);
17//! ```
18
19use cfg_if::cfg_if;
20use core::{mem, ptr};
21
22const fn cmp_max(a: usize, b: usize) -> usize {
23 if a > b { a } else { b }
24}
25
26// log2 of a power-of-two alignment, for `.p2align` (log2 on every object
27// format). All Rust alignments are powers of two, so this is exact.
28// Unused on fallback-only configurations.
29#[allow(dead_code)]
30const fn align_log2(align: usize) -> usize {
31 let mut log = 0;
32 let mut v = align;
33 while v > 1 {
34 v >>= 1;
35 log += 1;
36 }
37 log
38}
39
40/// Wrapper that folds a `const N: usize` key into the slot type.
41///
42/// `generic_static_const::<T, N>` is implemented as
43/// `generic_static::<ConstKey<T, N>>`.
44#[repr(transparent)]
45struct ConstKey<T, const N: usize>(T);
46
47// SAFETY: single transparent field; all-zero is valid iff it is valid for `T`.
48unsafe impl<T, const N: usize> bytemuck::Zeroable for ConstKey<T, N> where T: bytemuck::Zeroable {}
49
50// Dummy generic function generating a unique mangled symbol name per `(NS, T)`.
51// The `sym` operand in `slot_addr` extracts this name at compile time.
52// Never called; used only as a unique key.
53#[inline(never)]
54fn unique_symbol<NS: Namespace, T: 'static>() -> core::any::TypeId {
55 // Generate *unique* function body per (NS, T) so LLVM function merger or linker do not deduplicate
56 // code thus breaking "unique symbol" assumption.
57 core::any::TypeId::of::<(NS, T)>()
58}
59
60/// Raw address of the zero-initialized slot for `(NS, T)`.
61///
62/// Storage is defined with weak + COMDAT linkage in a per-symbol `.bss`
63/// section, the same way clang emits C++ template statics; the address is
64/// then computed with the optimal PC-relative sequence for the
65/// architecture. `#[inline(always)]` everywhere except the wasm32 fast path,
66/// so access optimizes down to the bare address computation with no call
67/// overhead.
68// Never inlined on the wasm32 fast path: the inline asm there switches to a
69// `.bss` section mid-function and must switch back to the exact enclosing
70// function section (`.previous` does not exist on wasm), which is only known
71// when the body stays in its own function.
72#[cfg_attr(all(target_family = "wasm", feature = "nightly"), inline(never))]
73#[cfg_attr(not(all(target_family = "wasm", feature = "nightly")), inline(always))]
74// Every fast path defines a named data label (`gen_static_{id}:`), which
75// the `named_asm_labels` lint denies by default. This is safe here: the
76// label is unique per `(NS, T)` and re-emission in the same object is
77// suppressed by the `.ifnotdef` guard.
78#[allow(named_asm_labels)]
79fn slot_addr<NS, T>() -> *mut T
80where
81 NS: Namespace,
82 T: 'static + bytemuck::Zeroable,
83{
84 #[allow(unused_assignments)]
85 let mut addr: *mut () = ptr::null_mut();
86
87 // 1) define the storage symbol (weak + COMDAT), selected by object
88 // format. The wasm32 fast path fuses its definition into the address
89 // block below instead
90 cfg_if! {
91 // Forced slow path: Cranelift backend or Miri.
92 if #[cfg(any(static_generics_fallback, miri))] {
93 // wasm32 fast path (`nightly` feature): storage is emitted
94 // by the fused block below, which must restore the exact enclosing
95 // function section afterwards.
96 } else if #[cfg(all(
97 target_family = "wasm",
98 target_arch = "wasm32",
99 feature = "nightly"
100 ))] {
101 // Apple (Mach-O): no COMDAT groups; the linker coalesces by
102 // `weak_definition` name. Mirrors clang (`__DATA,__data` even when
103 // zeroed, never `__common`).
104 } else if #[cfg(target_vendor = "apple")] {
105 unsafe {
106 core::arch::asm!(
107 ".ifnotdef gen_static_{id}",
108 ".pushsection __DATA,__data",
109 ".globl gen_static_{id}",
110 ".weak_definition gen_static_{id}",
111 ".p2align {p2align}, 0x0",
112 "gen_static_{id}:",
113 ".space {size}",
114 ".popsection",
115 ".endif",
116 size = const { cmp_max(mem::size_of::<T>(), 1) },
117 p2align = const { align_log2(mem::align_of::<T>()) },
118 id = sym unique_symbol::<NS, T>,
119 options(nostack, nomem)
120 );
121 }
122 // Windows/UEFI/Cygwin (COFF/PE): `discard` COMDAT (SELECT_ANY).
123 // Mirrors clang (`.section .bss,"bw",discard,"<sym>"` + `.globl`).
124 } else if #[cfg(any(
125 target_os = "windows",
126 target_os = "uefi",
127 target_os = "cygwin"
128 ))] {
129 unsafe {
130 core::arch::asm!(
131 ".ifnotdef gen_static_{id}",
132 ".pushsection .bss,\"bw\",discard,gen_static_{id}",
133 ".globl gen_static_{id}",
134 ".p2align {p2align}, 0x0",
135 "gen_static_{id}:",
136 ".zero {size}",
137 ".popsection",
138 ".endif",
139 size = const { cmp_max(mem::size_of::<T>(), 1) },
140 p2align = const { align_log2(mem::align_of::<T>()) },
141 id = sym unique_symbol::<NS, T>,
142 options(nostack, nomem)
143 );
144 }
145 // ELF: `weak` + named `.bss` COMDAT group. Mirrors clang
146 // (`.section .bss.<sym>,"awG",@nobits,<sym>,comdat`). Gated on the
147 // architectures with an address branch below; anything else —
148 // including AIX/XCOFF, which is not ELF — falls through to the slow
149 // fallback with no asm emitted.
150 } else if #[cfg(all(
151 any(
152 target_arch = "x86_64",
153 target_arch = "aarch64",
154 target_arch = "arm64ec",
155 target_arch = "x86",
156 target_arch = "arm",
157 target_arch = "riscv32",
158 target_arch = "riscv64",
159 target_arch = "loongarch64",
160 target_arch = "loongarch32",
161 target_arch = "powerpc64",
162 target_arch = "powerpc",
163 target_arch = "s390x"
164 ),
165 not(target_os = "aix")
166 ))] {
167 cfg_if! {
168 if #[cfg(target_arch = "arm")] {
169 unsafe {
170 core::arch::asm!(
171 ".ifnotdef gen_static_{id}",
172 ".pushsection .bss.gen_static_{id},\"awG\",%nobits,gen_static_{id},comdat",
173 ".weak gen_static_{id}",
174 ".hidden gen_static_{id}",
175 ".type gen_static_{id},%object",
176 ".p2align {p2align}, 0x0",
177 "gen_static_{id}:",
178 ".zero {size}",
179 ".size gen_static_{id}, {size}",
180 ".popsection",
181 ".endif",
182 size = const { cmp_max(mem::size_of::<T>(), 1) },
183 p2align = const { align_log2(mem::align_of::<T>()) },
184 id = sym unique_symbol::<NS, T>,
185 options(nostack, nomem)
186 );
187 }
188 } else {
189 unsafe {
190 core::arch::asm!(
191 ".ifnotdef gen_static_{id}",
192 ".pushsection .bss.gen_static_{id},\"awG\",@nobits,gen_static_{id},comdat",
193 ".weak gen_static_{id}",
194 ".hidden gen_static_{id}",
195 ".type gen_static_{id},@object",
196 ".p2align {p2align}, 0x0",
197 "gen_static_{id}:",
198 ".zero {size}",
199 ".size gen_static_{id}, {size}",
200 ".popsection",
201 ".endif",
202 size = const { cmp_max(mem::size_of::<T>(), 1) },
203 p2align = const { align_log2(mem::align_of::<T>()) },
204 id = sym unique_symbol::<NS, T>,
205 options(nostack, nomem)
206 );
207 }
208 }
209 }
210 } else {
211 }
212 }
213
214 // 2) compute the slot address (architecture-specific), or take the
215 // slow fallback.
216 cfg_if! {
217 // Forced slow path: Cranelift backend or Miri.
218 if #[cfg(any(static_generics_fallback, miri))] {
219 #[cfg(feature = "std")]
220 {
221 addr = crate::fallback::generic_static_fallback_mut::<NS, T>() as *mut T
222 as *mut ();
223 }
224 #[cfg(not(feature = "std"))]
225 core::compile_error!(
226 "static-generics: Cranelift/Miri needs the slow fallback (enable `std` feature)"
227 );
228 // wasm32 fast path (`nightly` feature): storage uses weak +
229 // COMDAT linkage in a per-symbol `.bss` section and the address is a single
230 // `i32.const` (linker resolves it via `R_WASM_MEMORY_ADDR`). `me`
231 // restores the exact enclosing function section afterwards (there is
232 // no `.previous` or `.popsection` on wasm), which is why `slot_addr`
233 // #[inline(never)]. However, wasm-opt and JIT should be able to inline this
234 // function anyways.
235 // Covers wasm32-unknown-unknown, wasm32-wasip1/wasip2,
236 // wasm32-unknown-emscripten, wasm32v1-none.
237 } else if #[cfg(all(
238 target_family = "wasm",
239 target_arch = "wasm32",
240 feature = "nightly"
241 ))] {
242 unsafe {
243 core::arch::asm!(
244 ".ifnotdef gen_static_{id}",
245 ".hidden gen_static_{id}",
246 ".type gen_static_{id},@object",
247 ".section .bss.gen_static_{id},\"G\",@,gen_static_{id},comdat",
248 ".weak gen_static_{id}",
249 ".p2align {align}, 0x0",
250 "gen_static_{id}:",
251 ".skip {size}, 0",
252 ".size gen_static_{id}, {size}",
253 ".section .text.{me},\"\",@",
254 ".endif",
255 "i32.const gen_static_{id}",
256 "local.set {x}",
257 size = const { cmp_max(mem::size_of::<T>(), 1) },
258 align = const { align_log2(mem::align_of::<T>()) },
259 id = sym unique_symbol::<NS, T>,
260 me = sym slot_addr::<NS, T>,
261 x = out(local) addr,
262 options(nostack, nomem)
263 );
264 }
265 // x86-64: RIP-relative LEA works on ELF, Mach-O and COFF, in
266 // both PIC and static relocation models. Covers all tier 1-3
267 // x86_64 targets (linux, windows, macos, freebsd, netbsd, uefi, etc).
268 } else if #[cfg(target_arch = "x86_64")] {
269 unsafe {
270 core::arch::asm!(
271 "lea {x}, [rip + gen_static_{id}]",
272 id = sym unique_symbol::<NS, T>,
273 x = out(reg) addr,
274 options(nostack, nomem)
275 );
276 }
277 // aarch64 + arm64ec, Apple (Mach-O): ADRP with @PAGE/@PAGEOFF.
278 // Covers macos, ios, tvos, watchos, visionos (all Apple, tier 1-3).
279 } else if #[cfg(all(target_arch = "aarch64", target_vendor = "apple"))] {
280 unsafe {
281 core::arch::asm!(
282 "adrp {x}, gen_static_{id}@PAGE",
283 "add {x}, {x}, gen_static_{id}@PAGEOFF",
284 id = sym unique_symbol::<NS, T>,
285 x = out(reg) addr,
286 options(nostack, nomem)
287 );
288 }
289 // aarch64 + arm64ec, non-Apple (ELF and COFF): ADRP with :lo12:.
290 // Covers linux, windows, freebsd, netbsd, openbsd, android, none, uefi, etc..
291 } else if #[cfg(all(
292 any(target_arch = "aarch64", target_arch = "arm64ec"),
293 not(target_vendor = "apple")
294 ))] {
295 unsafe {
296 core::arch::asm!(
297 "adrp {x}, gen_static_{id}",
298 "add {x}, {x}, :lo12:gen_static_{id}",
299 id = sym unique_symbol::<NS, T>,
300 x = out(reg) addr,
301 options(nostack, nomem)
302 );
303 }
304 // x86 (32-bit, i386/i586/i686), ELF PIC: GOT-relative via call/pop
305 // thunk + GOTOFF. Required because 32-bit x86 has no EIP-relative
306 // addressing; absolute R_386_32 is not allowed in PIE/shared.
307 } else if #[cfg(all(
308 target_arch = "x86",
309 not(any(
310 target_vendor = "apple",
311 target_os = "windows",
312 target_os = "uefi",
313 target_os = "cygwin",
314 target_os = "none"
315 ))
316 ))] {
317 unsafe {
318 core::arch::asm!(
319 "call 2f",
320 "2: popl {x}",
321 "addl $_GLOBAL_OFFSET_TABLE_+[.-2b], {x}",
322 "leal gen_static_{id}@GOTOFF({x}), {x}",
323 id = sym unique_symbol::<NS, T>,
324 x = out(reg) addr,
325 options(att_syntax, nomem)
326 );
327 }
328 // x86 (32-bit), Apple (Mach-O) PIC: call/pop + direct PC-relative
329 // LEA.
330 } else if #[cfg(all(target_arch = "x86", target_vendor = "apple"))] {
331 unsafe {
332 core::arch::asm!(
333 "call 2f",
334 "2: popl {x}",
335 "leal gen_static_{id}-2b({x}), {x}",
336 id = sym unique_symbol::<NS, T>,
337 x = out(reg) addr,
338 options(att_syntax, nomem)
339 );
340 }
341 // x86 (32-bit), COFF/PE and bare-metal static: absolute address.
342 // Windows/UEFI use base relocs (IMAGE_REL_I386_DIR32) so absolute
343 // is ASLR-compatible via loader fixups. `none` is static (no PIE).
344 // Covers windows-msvc/gnu/gnullvm, uefi, cygwin (tier 1-3).
345 } else if #[cfg(all(
346 target_arch = "x86",
347 any(
348 target_os = "windows",
349 target_os = "uefi",
350 target_os = "cygwin",
351 target_os = "none"
352 )
353 ))] {
354 unsafe {
355 core::arch::asm!(
356 "lea {x}, [gen_static_{id}]",
357 id = sym unique_symbol::<NS, T>,
358 x = out(reg) addr,
359 options(nostack, nomem)
360 );
361 }
362 // arm (32-bit, ARM/Thumb), COFF/PE: absolute via MOVW/MOVT.
363 // Covers thumbv7a-pc-windows-msvc, thumbv7a-uwp-windows-msvc, uefi.
364 } else if #[cfg(all(
365 target_arch = "arm",
366 any(target_os = "windows", target_os = "uefi", target_os = "cygwin")
367 ))] {
368 unsafe {
369 core::arch::asm!(
370 "movw {x}, :lower16:gen_static_{id}",
371 "movt {x}, :upper16:gen_static_{id}",
372 id = sym unique_symbol::<NS, T>,
373 x = out(reg) addr,
374 options(nostack, nomem)
375 );
376 }
377 // arm (32-bit), Thumb mode (thumbv7, thumbv6m, thumbv8m, ...),
378 // ELF/Mach-O/bare-metal: literal pool + `add r, pc`.
379 // Covers thumbv7neon-linux-gnueabihf, thumbv7em-none-eabi(hf),
380 // thumbv6m-none-eabi, thumbv8m-*, armv7s-ios, armv7k-watchos, etc.
381 } else if #[cfg(all(
382 target_arch = "arm",
383 not(any(target_os = "windows", target_os = "uefi", target_os = "cygwin")),
384 target_feature = "thumb-mode"
385 ))] {
386 unsafe {
387 core::arch::asm!(
388 "ldr {x}, 2f",
389 "1: add {x}, pc, {x}",
390 "b 3f",
391 "2: .word gen_static_{id}-1b-4",
392 "3:",
393 id = sym unique_symbol::<NS, T>,
394 x = out(reg) addr,
395 options(nostack, nomem)
396 );
397 }
398 // arm (32-bit), ARM mode (armv6/armv7, ...), ELF/Mach-O/bare-metal:
399 // Covers armv6/armv7-linux-gnueabi(hf), arm-freebsd/netbsd, none-eabi,
400 // android (arm-linux-androideabi, armv7-linux-androideabi), etc.
401 } else if #[cfg(all(
402 target_arch = "arm",
403 not(any(target_os = "windows", target_os = "uefi", target_os = "cygwin")),
404 not(target_feature = "thumb-mode")
405 ))] {
406 unsafe {
407 core::arch::asm!(
408 "ldr {x}, 2f",
409 "1: add {x}, pc, {x}",
410 "b 3f",
411 "2: .word gen_static_{id}-1b-8",
412 "3:",
413 id = sym unique_symbol::<NS, T>,
414 x = out(reg) addr,
415 options(nostack, nomem)
416 );
417 }
418 // riscv32 + riscv64: AUIPC + ADDI with %pcrel_hi/%pcrel_lo.
419 // PC-relative, works in PIC and static models, on all ELF OSes.
420 // Covers riscv64-linux-gnu/musl, riscv32-linux-gnu/musl,
421 // riscv*-none-elf, freebsd, netbsd, openbsd, nuttx, vxworks (tier 2-3).
422 } else if #[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))] {
423 unsafe {
424 core::arch::asm!(
425 "1: auipc {x}, %pcrel_hi(gen_static_{id})",
426 "addi {x}, {x}, %pcrel_lo(1b)",
427 id = sym unique_symbol::<NS, T>,
428 x = out(reg) addr,
429 options(nostack, nomem)
430 );
431 }
432 // loongarch64: PCALAU12I + ADDI.D with %pc_hi20/%pc_lo12.
433 // PC-relative, PIC- and static-compatible. Covers
434 // loongarch64-linux-gnu/musl/ohos, loongarch64-none (tier 2-3).
435 } else if #[cfg(target_arch = "loongarch64")] {
436 unsafe {
437 core::arch::asm!(
438 "pcalau12i {x}, %pc_hi20(gen_static_{id})",
439 "addi.d {x}, {x}, %pc_lo12(gen_static_{id})",
440 id = sym unique_symbol::<NS, T>,
441 x = out(reg) addr,
442 options(nostack, nomem)
443 );
444 }
445 // loongarch32: same as 64-bit but ADDI.W (32-bit addresses).
446 // Covers loongarch32-unknown-none(-softfloat) (tier 3).
447 } else if #[cfg(target_arch = "loongarch32")] {
448 unsafe {
449 core::arch::asm!(
450 "pcalau12i {x}, %pc_hi20(gen_static_{id})",
451 "addi.w {x}, {x}, %pc_lo12(gen_static_{id})",
452 id = sym unique_symbol::<NS, T>,
453 x = out(reg) addr,
454 options(nostack, nomem)
455 );
456 }
457 // powerpc64 (BE + LE, ELFv1/v2): TOC-relative via r2.
458 // r2 is the reserved TOC base; `sym@toc` is link-time TOC-relative.
459 // Covers powerpc64-linux-gnu/musl, powerpc64le-*, freebsd, openbsd (tier 2-3).
460 // AIX (XCOFF) excluded - different TOC ABI.
461 } else if #[cfg(all(target_arch = "powerpc64", not(target_os = "aix")))] {
462 unsafe {
463 core::arch::asm!(
464 "addis {x}, 2, gen_static_{id}@toc@ha",
465 "addi {x}, {x}, gen_static_{id}@toc@l",
466 id = sym unique_symbol::<NS, T>,
467 x = out(reg) addr,
468 options(nostack, nomem)
469 );
470 }
471 // powerpc (32-bit): pure PC-relative via BCL/MFLR thunk.
472 } else if #[cfg(all(target_arch = "powerpc", not(target_os = "aix")))] {
473 unsafe {
474 core::arch::asm!(
475 "bcl 20, 31, 1f",
476 "1: mflr {x}",
477 "addis {x}, {x}, (gen_static_{id}-1b)@ha",
478 "addi {x}, {x}, (gen_static_{id}-1b)@l",
479 id = sym unique_symbol::<NS, T>,
480 x = out(reg) addr,
481 out("lr") _,
482 options(nostack, nomem)
483 );
484 }
485 // s390x: LARL (load address relative long).
486 } else if #[cfg(target_arch = "s390x")] {
487 unsafe {
488 core::arch::asm!(
489 "larl {x}, gen_static_{id}",
490 id = sym unique_symbol::<NS, T>,
491 x = out(reg) addr,
492 options(nostack, nomem)
493 );
494 }
495 // Remaining architectures lack support for stable asm or do not allow to efficiently implement generic statics.
496 // The fallback to slowpath.
497 } else {
498 #[cfg(feature = "std")]
499 {
500 addr = crate::fallback::generic_static_fallback_mut::<NS, T>() as *mut T
501 as *mut ();
502 }
503 #[cfg(not(feature = "std"))]
504 core::compile_error!(
505 "static-generics is not supported on this target without the slow fallback (enable `std` feature)"
506 );
507 }
508 }
509
510 // Should error on unsupported targets
511 debug_assert!(!addr.is_null(), "unsupported platform");
512
513 addr.cast::<T>()
514}
515
516/// A namespace for generic statics.
517///
518/// # Safety
519///
520/// Implementing this trait is not unsafe per-se but you should use the [`crate::define_namespace`]
521/// instead.
522pub unsafe trait Namespace: 'static + Send + Sync + Copy + Clone {
523 /// The returned reference points to the static namespaced global variable for each
524 /// generic `T`. The static's value is zero-initialized.
525 ///
526 /// On targets with a fast weak-COMDAT + `asm!` path this compiles to 1-2
527 /// instructions with no runtime overhead. Anywhere else (unsupported
528 /// architectures, Miri, and the Cranelift backend) the `std` feature enables a
529 /// slow `Mutex<HashMap>`-based fallback; without it compilation fails with `compile_error!`.
530 #[inline(always)]
531 #[must_use]
532 fn generic_static<T>() -> &'static T
533 where
534 T: 'static + bytemuck::Zeroable,
535 {
536 unsafe { &*slot_addr::<Self, T>() }
537 }
538
539 /// The returned reference points to the static namespaced global variable for each
540 /// `(T, const N: usize)` pair (with lifetimes erased). The static's value is
541 /// zero-initialized.
542 ///
543 /// This is the `const`-keyed alternative to [`Namespace::generic_static`]: the same
544 /// `T` with a different `N` is a different address.
545 ///
546 /// ```rust
547 /// use static_generics::{define_namespace, namespace::Namespace};
548 /// use core::sync::atomic::{AtomicU64, Ordering};
549 ///
550 /// define_namespace!(Counters);
551 ///
552 /// Counters::generic_static_const::<AtomicU64, 0>().store(1, Ordering::Relaxed);
553 /// Counters::generic_static_const::<AtomicU64, 1>().store(2, Ordering::Relaxed);
554 /// assert_eq!(Counters::generic_static_const::<AtomicU64, 0>().load(Ordering::Relaxed), 1);
555 /// assert_eq!(Counters::generic_static_const::<AtomicU64, 1>().load(Ordering::Relaxed), 2);
556 /// ```
557 #[inline(always)]
558 #[must_use]
559 fn generic_static_const<T, const N: usize>() -> &'static T
560 where
561 T: 'static + bytemuck::Zeroable,
562 {
563 &Self::generic_static::<ConstKey<T, N>>().0
564 }
565
566 /// Raw (`*mut T`) view of the same slot as [`Namespace::generic_static`].
567 ///
568 /// Equivalent of `static mut` for static generics.
569 ///
570 /// # Safety
571 ///
572 /// The pointer itself is always valid (non-null, aligned, valid for `T`,
573 /// stable per `(Self, T)`, never dropped). The caller must follow `static mut`
574 /// safety rules while the pointer (or anything derived
575 /// from it) is used:
576 ///
577 /// * no other live reference — shared or mutable — to the same slot
578 /// aliases the access.
579 /// * no data races.
580 /// * the zero-initialized contents are a valid `T` ([`bytemuck::Zeroable`]),
581 /// and the slot is never dropped — do not store types needing `Drop`
582 /// (use `once` for those).
583 ///
584 /// ```rust
585 /// use static_generics::{define_namespace, namespace::Namespace};
586 ///
587 /// define_namespace!(Counters);
588 ///
589 /// let ptr = unsafe { Counters::generic_static_mut::<u64>() };
590 /// assert!(!ptr.is_null());
591 /// unsafe { ptr.write(7) };
592 /// assert_eq!(unsafe { ptr.read() }, 7);
593 /// // Same slot as the shared view.
594 /// assert!(core::ptr::eq(ptr as *const u64, Counters::generic_static::<u64>()));
595 /// ```
596 #[inline(always)]
597 unsafe fn generic_static_mut<T>() -> *mut T
598 where
599 T: 'static + bytemuck::Zeroable,
600 {
601 slot_addr::<Self, T>()
602 }
603
604 /// Raw (`*mut T`) view of the same slot as
605 /// [`Namespace::generic_static_const`]: the same `T` with a different `N`
606 /// is a different address.
607 ///
608 /// # Safety
609 ///
610 /// Same contract as [`Namespace::generic_static_mut`].
611 ///
612 /// ```rust
613 /// use static_generics::{define_namespace, namespace::Namespace};
614 ///
615 /// define_namespace!(Counters);
616 ///
617 /// let a = unsafe { Counters::generic_static_const_mut::<u64, 0>() };
618 /// let b = unsafe { Counters::generic_static_const_mut::<u64, 1>() };
619 /// assert!(!core::ptr::eq(a, b));
620 /// unsafe { a.write(1) };
621 /// unsafe { b.write(2) };
622 /// assert_eq!(unsafe { a.read() }, 1);
623 /// ```
624 #[inline(always)]
625 unsafe fn generic_static_const_mut<T, const N: usize>() -> *mut T
626 where
627 T: 'static + bytemuck::Zeroable,
628 {
629 // SAFETY: `ConstKey` is `#[repr(transparent)]` over `T`, so casting
630 // `*mut ConstKey<T, N>` to `*mut T` keeps the same address.
631 unsafe { Self::generic_static_mut::<ConstKey<T, N>>().cast::<T>() }
632 }
633}
634
635/// Extensions on top of [`Namespace::generic_static`] to make using
636/// generic statics easier.
637pub trait NamespaceExt: Namespace {
638 /// Alias for [`Namespace::generic_static`].
639 #[inline(always)]
640 #[must_use]
641 fn get<T>() -> &'static T
642 where
643 T: 'static + bytemuck::Zeroable,
644 {
645 Self::generic_static::<T>()
646 }
647
648 /// Alias for [`Namespace::generic_static_mut`].
649 ///
650 /// # Safety
651 ///
652 /// Same as [`Namespace::generic_static_mut`].
653 #[inline(always)]
654 unsafe fn get_mut<T>() -> *mut T
655 where
656 T: 'static + bytemuck::Zeroable,
657 {
658 // SAFETY: forwarded from the caller upholding the aliasing contract.
659 unsafe { Self::generic_static_mut::<T>() }
660 }
661
662 /// Lazily-initialized generic static for types that are *not* [`Zeroable`](bytemuck::Zeroable).
663 ///
664 /// Backed by [`crate::once::OnceSlot`]: the first call runs `init` and
665 /// later calls (with the same `NS` and `T`) return the same address.
666 ///
667 /// ```rust
668 /// use static_generics::{define_namespace, namespace::NamespaceExt};
669 ///
670 /// define_namespace!(MyNs);
671 ///
672 /// let one = MyNs::once::<String>(|| String::from("hello"));
673 /// let two = MyNs::once::<String>(|| String::from("ignored"));
674 /// assert_eq!(one.as_str(), "hello");
675 /// assert!(core::ptr::eq(one, two));
676 /// ```
677 #[must_use]
678 fn once<T>(init: impl FnOnce() -> T) -> &'static T
679 where
680 T: 'static + Send + Sync,
681 {
682 Self::generic_static::<crate::once::OnceSlot<T>>().get_or_init(init)
683 }
684
685 /// Alias for [`Namespace::generic_static_const`].
686 #[inline(always)]
687 #[must_use]
688 fn get_const<T, const N: usize>() -> &'static T
689 where
690 T: 'static + bytemuck::Zeroable,
691 {
692 Self::generic_static_const::<T, N>()
693 }
694
695 /// Alias for [`Namespace::generic_static_const_mut`].
696 ///
697 /// # Safety
698 ///
699 /// Same as [`Namespace::generic_static_mut`].
700 #[inline(always)]
701 unsafe fn get_const_mut<T, const N: usize>() -> *mut T
702 where
703 T: 'static + bytemuck::Zeroable,
704 {
705 // SAFETY: forwarded from the caller upholding the aliasing contract.
706 unsafe { Self::generic_static_const_mut::<T, N>() }
707 }
708
709 /// Lazily-initialized generic static keyed by `(T, const N: usize)`.
710 ///
711 /// Like [`NamespaceExt::once`], but the same `T` with a different `N`
712 /// is a different address. Backed by [`crate::once::OnceSlot`].
713 #[must_use]
714 fn once_const<T, const N: usize>(init: impl FnOnce() -> T) -> &'static T
715 where
716 T: 'static + Send + Sync,
717 {
718 Self::generic_static_const::<crate::once::OnceSlot<T>, N>().get_or_init(init)
719 }
720}
721
722impl<NS: Namespace> NamespaceExt for NS {}
723
724/// Define a namespace for static generics. Namespace is like "scope" for generics. Were you to always use [`DefaultNamespace`](crate::DefaultNamespace)
725/// you would end up with eventually running out of static slots. Defining multiple namespaces allows you to have virtually unlimited
726/// set of static generics per each usage.
727#[macro_export]
728macro_rules! define_namespace {
729 ($vis:vis $name:ident) => {
730 #[derive(Debug, Copy, Clone)]
731 $vis struct $name;
732
733 unsafe impl $crate::namespace::Namespace for $name {}
734 };
735}
736
737/// Declare a typed accessor function for one generic static.
738///
739/// ```rust
740/// use static_generics::{define_namespace, define_static};
741/// use core::sync::atomic::AtomicU64;
742///
743/// define_namespace!(MyNs);
744/// define_static!(pub fn counter<T>() -> AtomicU64; in MyNs);
745///
746/// counter::<u32>().fetch_add(1, core::sync::atomic::Ordering::Relaxed);
747/// ```
748#[macro_export]
749macro_rules! define_static {
750 ($vis:vis fn $name:ident () -> *mut $ty:ty ; in $ns:ty) => {
751 $vis unsafe fn $name() -> *mut $ty
752 where
753 $ty: $crate::Zeroable,
754 {
755 // SAFETY: caller must follow `static mut` safety rules
756 unsafe { <$ns as $crate::namespace::Namespace>::generic_static_mut::<$ty>() }
757 }
758 };
759 ($vis:vis fn $name:ident <$($gen:ident),+ $(,)?> () -> *mut $ty:ty ; in $ns:ty) => {
760 $vis unsafe fn $name<$($gen: 'static),+>() -> *mut $ty
761 where
762 $ty: $crate::Zeroable,
763 {
764 // SAFETY: caller must follow `static mut` safety rules
765 unsafe { <$ns as $crate::namespace::Namespace>::generic_static_mut::<$ty>() }
766 }
767 };
768 ($vis:vis fn $name:ident <const $N:ident : usize $(,)?> () -> *mut $ty:ty ; in $ns:ty) => {
769 $vis unsafe fn $name<const $N: usize>() -> *mut $ty
770 where
771 $ty: $crate::Zeroable,
772 {
773 // SAFETY: caller must follow `static mut` safety rules
774 unsafe { <$ns as $crate::namespace::Namespace>::generic_static_const_mut::<$ty, $N>() }
775 }
776 };
777 ($vis:vis fn $name:ident <$gen:ident, const $N:ident : usize $(,)?> () -> *mut $ty:ty ; in $ns:ty) => {
778 $vis unsafe fn $name<$gen: 'static, const $N: usize>() -> *mut $ty
779 where
780 $ty: $crate::Zeroable,
781 {
782 // SAFETY: caller must follow `static mut` safety rules
783 unsafe { <$ns as $crate::namespace::Namespace>::generic_static_const_mut::<$ty, $N>() }
784 }
785 };
786 ($vis:vis fn $name:ident () -> $ty:ty ; in $ns:ty) => {
787 $vis fn $name() -> &'static $ty
788 where
789 $ty: $crate::Zeroable,
790 {
791 <$ns as $crate::namespace::Namespace>::generic_static::<$ty>()
792 }
793 };
794 ($vis:vis fn $name:ident <$($gen:ident),+ $(,)?> () -> $ty:ty ; in $ns:ty) => {
795 $vis fn $name<$($gen: 'static),+>() -> &'static $ty
796 where
797 $ty: $crate::Zeroable,
798 {
799 <$ns as $crate::namespace::Namespace>::generic_static::<$ty>()
800 }
801 };
802 ($vis:vis fn $name:ident <const $N:ident : usize $(,)?> () -> $ty:ty ; in $ns:ty) => {
803 $vis fn $name<const $N: usize>() -> &'static $ty
804 where
805 $ty: $crate::Zeroable,
806 {
807 <$ns as $crate::namespace::Namespace>::generic_static_const::<$ty, $N>()
808 }
809 };
810 ($vis:vis fn $name:ident <$gen:ident, const $N:ident : usize $(,)?> () -> $ty:ty ; in $ns:ty) => {
811 $vis fn $name<$gen: 'static, const $N: usize>() -> &'static $ty
812 where
813 $ty: $crate::Zeroable,
814 {
815 <$ns as $crate::namespace::Namespace>::generic_static_const::<$ty, $N>()
816 }
817 };
818}