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}