Skip to main content

static_generics/
namespace.rs

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