Skip to main content

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}