Skip to main content

azul_core/
lib.rs

1//! Shared datatypes for azul-* crates
2//!
3//! `azul-core` provides the platform-independent core types used throughout
4//! the Azul toolkit. Key modules include [`dom`] for DOM construction,
5//! [`callbacks`] for event callback types, [`styled_dom`] for the CSSOM,
6//! and [`window`] for OS windowing abstractions.
7//!
8//! This crate depends on [`azul_css`] for CSS property definitions and is
9//! consumed by `azul-layout`, `azul-dll`, and the platform shell crates.
10//! It supports `no_std` environments via `#![cfg_attr(not(feature = "std"), no_std)]`.
11
12#![cfg_attr(not(feature = "std"), no_std)]
13// Lint policy: deny correctness/safety issues, warn on style
14#![deny(unused_must_use)]
15#![warn(clippy::all)]
16// Extreme-lint lockdown: all clippy groups plus opt-in rustc lints, enforced as
17// -D warnings on library code by the CI clippy job. Test builds are exempt via
18// cfg(not(test)) below since the set is high-noise and low-value on unit and
19// generated tests; clippy::all correctness still applies to test code.
20// (clippy::restriction wholesale + unused_results + box_pointers deliberately
21// omitted — contradictory / overwhelmingly noisy by design.)
22#![cfg_attr(not(test), warn(
23    clippy::pedantic,
24    clippy::nursery,
25    clippy::cargo,
26    // missing_docs,  // TODO(docs): re-enable as a dedicated final docs pass; disabled
27    //                // for now so the cleanup focuses on code-quality lints, not doc debt.
28    missing_debug_implementations,
29    missing_copy_implementations,
30    unreachable_pub,
31    unused_qualifications,
32    unused_lifetimes,
33    unused_import_braces,
34    unused_macro_rules,
35    unused_crate_dependencies,
36    meta_variable_misuse,
37    trivial_casts,
38    trivial_numeric_casts,
39    elided_lifetimes_in_paths,
40    single_use_lifetimes,
41    variant_size_differences,
42    non_ascii_idents,
43    unsafe_op_in_unsafe_fn,
44    let_underscore_drop,
45))]
46// `multiple_crate_versions` (implied by clippy::cargo) flags transitive
47// dependency-version dups that cannot be resolved in azul's own source:
48// `syn` 1.0.x ↔ 2.0.x (the proc-macro ecosystem is mid-migration; both are
49// pulled in transitively). Documented allow — re-audit when the dep tree aligns.
50#![allow(clippy::multiple_crate_versions)]
51#![allow(
52    clippy::non_canonical_partial_ord_impl,
53    clippy::legacy_numeric_constants,
54    clippy::should_implement_trait,
55    clippy::result_unit_err,
56    clippy::ptr_as_ptr,
57    clippy::too_many_arguments,
58    clippy::type_complexity,
59    unused_imports,
60    unused_variables,
61    unused_mut,
62    unused_parens,
63    dead_code,
64    unused_doc_comments,
65    unused_assignments,                    // compact_cache_builder incremental updates
66    unexpected_cfgs,
67    unpredictable_function_pointer_comparisons, // intentional in dom callback comparison
68    improper_ctypes_definitions,           // xml component fns use Rust fn pointers internally
69    static_mut_refs,                       // TODO: migrate to OnceLock for Rust 2024
70)]
71
72// `extern crate` + `#[macro_use]` required for `no_std` support:
73// makes `core` and `alloc` macros available without `use` imports.
74#[macro_use]
75extern crate core;
76#[macro_use]
77extern crate alloc;
78#[macro_use]
79extern crate azul_css;
80
81/// Internal macros for `Vec`, `Option`, and callback boilerplate.
82///
83#[macro_use]
84pub mod macros;
85/// Debug logging system with category filtering.
86#[macro_use]
87pub mod debug;
88/// SQL database POD types — `DbValue` + `DbRows` (engine-agnostic). The
89/// `Db` handle + SQLite engine live in `azul_dll` behind `db-sqlite`.
90pub mod db;
91/// Unified `AZ_PROFILE` gate for memory and CPU profiling instrumentation.
92pub mod profile;
93/// `no_std`-friendly synchronization primitives.
94///
95/// In `std` builds these re-export the matching `std::sync` types. In
96/// `no_std` builds they provide minimal spinlock-backed equivalents
97/// implementing only the API surface azul-core relies on.
98pub mod sync {
99    #[cfg(feature = "std")]
100    pub use std::sync::OnceLock;
101
102    #[cfg(not(feature = "std"))]
103    pub use self::nostd::OnceLock;
104
105    #[cfg(not(feature = "std"))]
106    mod nostd {
107        use core::cell::UnsafeCell;
108        use core::sync::atomic::{AtomicU8, Ordering};
109
110        const UNINIT: u8 = 0;
111        const BUSY: u8 = 1;
112        const READY: u8 = 2;
113
114        /// Minimal `no_std` `OnceLock` mirroring the slice of the std API used
115        /// by azul-core (`new`, `get`, `get_or_init`).
116        pub struct OnceLock<T> {
117            state: AtomicU8,
118            value: UnsafeCell<Option<T>>,
119        }
120
121        unsafe impl<T: Send + Sync> Sync for OnceLock<T> {}
122        unsafe impl<T: Send> Send for OnceLock<T> {}
123
124        impl<T> OnceLock<T> {
125            pub const fn new() -> Self {
126                OnceLock {
127                    state: AtomicU8::new(UNINIT),
128                    value: UnsafeCell::new(None),
129                }
130            }
131
132            pub fn get(&self) -> Option<&T> {
133                if self.state.load(Ordering::Acquire) == READY {
134                    unsafe { (*self.value.get()).as_ref() }
135                } else {
136                    None
137                }
138            }
139
140            pub fn get_or_init<F: FnOnce() -> T>(&self, f: F) -> &T {
141                if let Some(v) = self.get() {
142                    return v;
143                }
144                // Contend for the right to initialize.
145                while self
146                    .state
147                    .compare_exchange(UNINIT, BUSY, Ordering::Acquire, Ordering::Acquire)
148                    .is_err()
149                {
150                    if self.state.load(Ordering::Acquire) == READY {
151                        return self.get().expect("OnceLock ready");
152                    }
153                    core::hint::spin_loop();
154                }
155                unsafe {
156                    *self.value.get() = Some(f());
157                }
158                self.state.store(READY, Ordering::Release);
159                self.get().expect("OnceLock initialized")
160            }
161        }
162
163        impl<T> Default for OnceLock<T> {
164            fn default() -> Self {
165                Self::new()
166            }
167        }
168
169        impl<T: core::fmt::Debug> core::fmt::Debug for OnceLock<T> {
170            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
171                f.debug_tuple("OnceLock").field(&self.get()).finish()
172            }
173        }
174
175        impl<T: Clone> Clone for OnceLock<T> {
176            fn clone(&self) -> Self {
177                let new = OnceLock::new();
178                if let Some(v) = self.get() {
179                    let _ = new.get_or_init(|| v.clone());
180                }
181                new
182            }
183        }
184
185        impl<T: PartialEq> PartialEq for OnceLock<T> {
186            fn eq(&self, other: &Self) -> bool {
187                self.get() == other.get()
188            }
189        }
190    }
191}
192
193/// `no_std`-friendly default hasher used for change-detection hashing.
194///
195/// In `std` builds this re-exports `std::hash::DefaultHasher` so behaviour
196/// is unchanged. In `no_std` builds it provides a small deterministic
197/// `FxHasher`-style hasher implementing `core::hash::Hasher`. The values are
198/// only required to be stable within a single process run (they back diffing /
199/// change detection), not to match `std`'s `SipHash` output.
200pub mod hash {
201    #[cfg(feature = "std")]
202    pub use std::hash::DefaultHasher;
203
204    #[cfg(not(feature = "std"))]
205    pub use self::nostd::DefaultHasher;
206
207    #[cfg(not(feature = "std"))]
208    mod nostd {
209        use core::hash::Hasher;
210
211        const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
212        const ROTATE: u32 = 5;
213
214        /// FxHasher-style `no_std` hasher. Not DoS-resistant; used purely for
215        /// in-process change detection.
216        #[derive(Default)]
217        pub struct DefaultHasher {
218            hash: u64,
219        }
220
221        impl DefaultHasher {
222            pub fn new() -> Self {
223                DefaultHasher { hash: 0 }
224            }
225
226            #[inline]
227            fn add(&mut self, word: u64) {
228                self.hash = (self.hash.rotate_left(ROTATE) ^ word).wrapping_mul(SEED);
229            }
230        }
231
232        impl Hasher for DefaultHasher {
233            #[inline]
234            fn finish(&self) -> u64 {
235                self.hash
236            }
237
238            #[inline]
239            fn write(&mut self, bytes: &[u8]) {
240                for chunk in bytes.chunks(8) {
241                    let mut buf = [0u8; 8];
242                    buf[..chunk.len()].copy_from_slice(chunk);
243                    self.add(u64::from_le_bytes(buf));
244                }
245            }
246
247            #[inline]
248            fn write_u8(&mut self, i: u8) {
249                self.add(i as u64);
250            }
251            #[inline]
252            fn write_u64(&mut self, i: u64) {
253                self.add(i);
254            }
255            #[inline]
256            fn write_usize(&mut self, i: usize) {
257                self.add(i as u64);
258            }
259        }
260    }
261}
262/// Callback types: layout, event, timer, thread, and focus handling.
263#[macro_use]
264pub mod callbacks;
265/// Host-language callback invoker registry.
266///
267/// The C-ABI surface managed-FFI bindings (Lua, Ruby, …) use to register one
268/// per-kind invoker + a single shared releaser, so callbacks can be created via
269/// `_createFromHostHandle` without the host having to generate trampolines for
270/// struct-by-value signatures their FFI library can't handle.
271#[macro_use]
272pub mod host_invoker;
273pub mod a11y;
274/// Accessibility types for screen-reader integration (AccessKit).
275/// DOM-morph animation.
276///
277/// Interpolation core (springs + easing), FLIP geometry, and the keyed
278/// store of in-flight animations..
279pub mod animation;
280/// Audio POD types — `AudioConfig` (stream format) + `AudioFrame` (interleaved
281/// f32 samples).
282///
283/// The unit captured from the mic, played back, and (P8) shared
284/// over UDP. Backend (rodio / cpal / AVAudioEngine / AAudio) lives dll-side.
285pub mod audio;
286/// Biometric-auth POD types — `BiometricKind` + `BiometricResult` + `BiometricPrompt`.
287///
288/// Stateful manager lives in `azul_layout::managers::biometric`.
289pub mod biometric;
290/// Camera-capture POD types — `CaptureStreamId` + `CameraConfig` +
291/// `CameraFacing` + `StreamState` + … .
292///
293/// The stateful `CameraStream` /
294/// `CameraManager` (which own the shared `ImageRef` texture) live in
295/// `azul_layout::managers::camera`.
296pub mod camera;
297/// Converts `CssPropertyCache` into compact three-tier numeric cache.
298pub mod compact;
299/// Linear-time DOM diffing for incremental updates.
300pub mod diagnostics;
301pub mod diff;
302/// DOM construction: `Dom`, `NodeData`, `NodeType`, and the CSS-in-Rust API.
303pub mod dom;
304/// Drag context for text selection, scrollbar, node, and window drags.
305pub mod drag;
306/// Event filtering: mouse, keyboard, window, and synthetic events.
307pub mod events;
308/// Gamepad POD types — `GamepadId` + `GamepadButton` + `GamepadAxis` +
309/// `GamepadState`.
310///
311/// Stateful manager lives in `azul_layout::managers::gamepad`.
312pub mod gamepad;
313pub mod haptics;
314pub mod hid;
315/// Geolocation POD types — `LocationFix` + `GeolocationProbeConfig`.
316///
317/// Stateful manager lives in `azul_layout::managers::geolocation`.
318pub mod geolocation;
319/// Logical and physical coordinate types (`LogicalSize`, `PhysicalPosition`, etc.).
320pub mod geom;
321/// OpenGL context wrappers, shader compilation, and texture cache.
322///
323pub mod gl;
324/// FXAA (Fast Approximate Anti-Aliasing) shader.
325pub mod gl_fxaa;
326/// OpenGL constants (GL 1.1 through GL 4.x).
327pub mod glconst;
328/// GPU value cache for CSS transforms and opacity.
329pub mod gpu;
330/// Hit-test results (which DOM nodes are under the cursor) + the type-safe
331/// hit-test tag system for compositor integration (merged from `hit_test_tag`).
332///
333pub mod hit_test;
334/// Icon provider system for loading icons from fonts, images, or zip packs.
335pub mod icon;
336/// Arena-based node tree storage and hierarchy management.
337pub mod id;
338/// JSON value types for the C API (no serde dependency).
339pub mod json;
340/// System-keyring POD types — `KeyringRequest` + `KeyringResult`.
341///
342/// Stateful manager lives in `azul_layout::managers::keyring`.
343pub mod keyring;
344/// Runtime log filtering: per-level and per-category atomics.
345///
346/// Parsed from `AZ_LOG` but changeable while the process runs. Logging is gated HERE and
347/// never by a cargo feature — see the module docs for the 2026-08-07 incident
348/// that made a compile-time gate delete the one diagnosis that was needed.
349pub mod log_filter;
350/// Form constraint validation - the `ValidityState` an `Invalid` event
351/// explains itself with. The rules live in `azul_layout::form`.
352pub mod form;
353/// Menu system: context menus, dropdown menus, and menu bars.
354pub mod menu;
355/// Media playback POD types — the `PlaybackState` the six media events
356/// describe (11c).
357///
358/// Stateful manager lives in `azul_layout::managers::media_player`.
359pub mod media_player;
360/// The system media session — what the desktop's media widget shows. The
361/// app-facing half of the media-key transport in `dll/desktop/extra/media_keys`.
362pub mod media_session;
363/// Paged-media primitives: the `FragmentationContext` (continuous vs. paged) and
364/// `PageMargins`. The pagination/slicing logic lives in `azul_layout::solver3`.
365pub mod paged;
366/// SVG `d=""` path data parser.
367pub mod path_parser;
368/// CSS property cache for efficient per-node style resolution.
369pub mod physical_key;
370pub mod prop_cache;
371/// Type-erased, ref-counted smart pointer with runtime borrow checking.
372pub mod refany;
373/// Resource management: font/image loading, caching, and garbage collection.
374pub mod resources;
375/// Screen-capture POD types — `ScreenCaptureSource` + `ScreenCaptureConfig`.
376///
377/// Symmetric to the camera surface (a "dumb widget" in
378/// `azul_layout::widgets::screencap`); reuses `camera`'s capture status types.
379pub mod screencap;
380/// Text selection and cursor positioning for inline content.
381pub mod selection;
382/// Motion-sensor POD types — `SensorKind` + `SensorReading`.
383///
384/// Stateful manager lives in `azul_layout::managers::sensors`.
385pub mod sensors;
386/// Pointer coordinate spaces as distinct, zero-cost newtypes.
387///
388/// Window space, static layout space, border-box-local, content-box-local and
389/// scrolled content, plus the explicit ancestor-walk [`spaces::Inclusivity`].
390pub mod spaces;
391/// CSS cascade: selector matching, specificity, and property inheritance.
392pub mod style;
393/// `StyledDom` — the result of applying CSS to a DOM tree (the CSSOM).
394pub mod styled_dom;
395/// SVG rendering, path tessellation, and geometric operations.
396pub mod svg;
397/// Timer, thread, and async task management.
398pub mod task;
399/// 3D transform matrix computation for CSS transforms.
400pub mod transform;
401pub mod transient;
402/// System tray / status icon POD types.
403///
404/// Icon bitmaps, category/status and the tray event kinds. The OS plumbing
405/// lives in `azul-dll` (`desktop/tray`).
406pub mod tray;
407/// Built-in user-agent default stylesheet.
408pub mod ua_css;
409/// Default font/text constants and small geometry helpers for layout.
410pub mod ui_solver;
411/// URL POD type (`Url`/`UrlParseError`); parsing gated behind the `url` feature.
412pub mod url;
413/// Video-playback POD types — `VideoConfig` (source URL + autoplay/loop).
414///
415/// Same "dumb widget" architecture (`azul_layout::widgets::video`); decoded
416/// via vk-video into the shared GL texture.
417pub mod video;
418/// Window configuration, input state, and platform-specific options.
419pub mod window;
420/// XML and XHTML parsing for declarative UI definitions.
421pub mod xml;
422
423/// Ordered map alias used throughout `azul-core`.
424///
425/// This is backed by `BTreeMap` (not a hash map) because the `core` crate
426/// supports `no_std`, where `HashMap` is unavailable. The webrender crates
427/// define their own `FastHashMap` using `HashMap` + `FxHasher`.
428pub type OrderedMap<T, U> = alloc::collections::BTreeMap<T, U>;
429pub type FastBTreeSet<T> = alloc::collections::BTreeSet<T>;
430
431#[cfg(test)]
432#[allow(clippy::pedantic, clippy::nursery)]
433mod autotest_generated {
434    use alloc::{boxed::Box, string::String, vec::Vec};
435    use core::{
436        cell::Cell,
437        hash::{Hash, Hasher},
438    };
439
440    use super::{hash::DefaultHasher, sync::OnceLock, FastBTreeSet, OrderedMap};
441
442    // NOTE: `sync::OnceLock` and `hash::DefaultHasher` are *aliases*: with the
443    // (default) `std` feature they re-export `std::sync::OnceLock` /
444    // `std::hash::DefaultHasher`; without it they resolve to the hand-written
445    // `no_std` shims in this file. Tests below are split accordingly:
446    //   * un-gated  -> the API contract BOTH impls must satisfy,
447    //   * cfg-gated -> behaviour that is specific to one impl.
448    // `DefaultHasher::add` is private to the private `hash::nostd` module, so it
449    // is not nameable from here; `write_u64` forwards to it 1:1 and is used as
450    // the proxy for the numeric/overflow cases.
451
452    // ---------------------------------------------------------------
453    // OnceLock — constructor / getter invariants
454    // ---------------------------------------------------------------
455
456    #[test]
457    fn oncelock_new_is_empty() {
458        let cell: OnceLock<u32> = OnceLock::new();
459        assert!(cell.get().is_none());
460        // getter must stay pure: repeated reads never initialize
461        assert!(cell.get().is_none());
462    }
463
464    #[test]
465    fn oncelock_new_is_usable_in_const_context() {
466        static CELL: OnceLock<u64> = OnceLock::new();
467        assert!(CELL.get().is_none());
468        assert_eq!(*CELL.get_or_init(|| u64::MAX), u64::MAX);
469        assert_eq!(CELL.get().copied(), Some(u64::MAX));
470    }
471
472    #[test]
473    fn oncelock_default_matches_new() {
474        let cell: OnceLock<Vec<u8>> = OnceLock::default();
475        assert!(cell.get().is_none());
476    }
477
478    #[test]
479    fn oncelock_get_or_init_runs_closure_exactly_once() {
480        let calls = Cell::new(0usize);
481        let cell: OnceLock<u32> = OnceLock::new();
482
483        assert_eq!(
484            *cell.get_or_init(|| {
485                calls.set(calls.get() + 1);
486                7
487            }),
488            7
489        );
490        // The second/third call must return the FIRST value and never re-run `f`.
491        assert_eq!(
492            *cell.get_or_init(|| {
493                calls.set(calls.get() + 1);
494                9
495            }),
496            7
497        );
498        assert_eq!(
499            *cell.get_or_init(|| {
500                calls.set(calls.get() + 1);
501                11
502            }),
503            7
504        );
505        assert_eq!(calls.get(), 1);
506        assert_eq!(cell.get().copied(), Some(7));
507    }
508
509    #[test]
510    fn oncelock_get_and_get_or_init_alias_the_same_storage() {
511        let cell: OnceLock<u32> = OnceLock::new();
512        let a: *const u32 = cell.get_or_init(|| 1);
513        let b: *const u32 = cell.get().expect("initialized");
514        let c: *const u32 = cell.get_or_init(|| 2);
515        // The value must never be moved/duplicated by a second init attempt.
516        assert_eq!(a, b);
517        assert_eq!(a, c);
518    }
519
520    #[test]
521    fn oncelock_holds_zero_sized_type() {
522        // ZST: `Option<()>` has no payload bits, so a naive impl can confuse
523        // "initialized" with "None".
524        let cell: OnceLock<()> = OnceLock::new();
525        assert!(cell.get().is_none());
526        cell.get_or_init(|| ());
527        assert!(cell.get().is_some());
528    }
529
530    #[test]
531    fn oncelock_holds_large_payload() {
532        let cell: OnceLock<Box<[u8]>> = OnceLock::new();
533        let v = cell.get_or_init(|| alloc::vec![0xABu8; 1 << 20].into_boxed_slice());
534        assert_eq!(v.len(), 1 << 20);
535        assert!(v.iter().all(|b| *b == 0xAB));
536        assert_eq!(cell.get().map(|b| b.len()), Some(1 << 20));
537    }
538
539    #[test]
540    fn oncelock_holds_nan_without_eq_confusion() {
541        let cell: OnceLock<f64> = OnceLock::new();
542        // `NaN != NaN`, so initialization must be tracked by state, not by
543        // comparing the payload against a sentinel.
544        assert!(cell.get_or_init(|| f64::NAN).is_nan());
545        assert!(cell.get().is_some_and(|f| f.is_nan()));
546        // A second init must not overwrite the stored NaN with 1.0.
547        assert!(cell.get_or_init(|| 1.0).is_nan());
548    }
549
550    #[test]
551    fn oncelock_clone_copies_state_not_aliases_it() {
552        let cell: OnceLock<String> = OnceLock::new();
553
554        let empty = cell.clone();
555        assert!(empty.get().is_none());
556        // Initializing the source must not retro-fill an earlier clone.
557        cell.get_or_init(|| String::from("azul"));
558        assert!(empty.get().is_none());
559
560        let full = cell.clone();
561        assert_eq!(full.get().map(String::as_str), Some("azul"));
562        // Distinct storage: the clone must own its own allocation.
563        assert_ne!(
564            cell.get().expect("init") as *const String,
565            full.get().expect("init") as *const String
566        );
567    }
568
569    #[test]
570    fn oncelock_eq_compares_contents() {
571        let a: OnceLock<u32> = OnceLock::new();
572        let b: OnceLock<u32> = OnceLock::new();
573        assert_eq!(a, b); // both empty
574
575        a.get_or_init(|| 5);
576        assert_ne!(a, b); // Some(5) vs None
577
578        b.get_or_init(|| 5);
579        assert_eq!(a, b);
580
581        let c: OnceLock<u32> = OnceLock::new();
582        c.get_or_init(|| 6);
583        assert_ne!(a, c);
584    }
585
586    #[cfg(feature = "std")]
587    #[test]
588    fn oncelock_concurrent_get_or_init_initializes_exactly_once() {
589        use std::sync::{
590            atomic::{AtomicUsize, Ordering},
591            Barrier,
592        };
593
594        const THREADS: usize = 8;
595
596        let cell: OnceLock<usize> = OnceLock::new();
597        let inits = AtomicUsize::new(0);
598        let gate = Barrier::new(THREADS);
599
600        std::thread::scope(|s| {
601            for id in 0..THREADS {
602                let (cell, inits, gate) = (&cell, &inits, &gate);
603                let _ = s.spawn(move || {
604                    gate.wait(); // maximize contention on the CAS
605                    let v = *cell.get_or_init(|| {
606                        inits.fetch_add(1, Ordering::SeqCst);
607                        id
608                    });
609                    // Every racer must observe the same winner.
610                    assert_eq!(v, *cell.get().expect("initialized after get_or_init"));
611                    v
612                });
613            }
614        });
615
616        assert_eq!(inits.load(Ordering::SeqCst), 1);
617        let winner = cell.get().copied().expect("initialized");
618        assert!(winner < THREADS);
619    }
620
621    // The `std` OnceLock documents that a panicking `f` leaves the cell
622    // *uninitialized* (and re-initializable) rather than poisoned.
623    //
624    // The `no_std` shim in this file does NOT hold this property: it leaves
625    // `state == BUSY`, so any later `get_or_init` spins forever. This test is
626    // therefore std-gated on purpose — running it under `no_std` would hang the
627    // test binary instead of failing it.
628    #[cfg(feature = "std")]
629    #[test]
630    fn oncelock_panicking_initializer_leaves_cell_reusable() {
631        let cell: OnceLock<u32> = OnceLock::new();
632
633        let prev = std::panic::take_hook();
634        std::panic::set_hook(Box::new(|_| {})); // keep the expected panic quiet
635        let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
636            cell.get_or_init(|| panic!("initializer blew up"));
637        }));
638        std::panic::set_hook(prev);
639
640        assert!(caught.is_err(), "the panic must propagate to the caller");
641        assert!(cell.get().is_none(), "cell must remain uninitialized");
642        assert_eq!(*cell.get_or_init(|| 42), 42, "cell must still be usable");
643    }
644
645    // ---------------------------------------------------------------
646    // DefaultHasher — construction / determinism
647    // ---------------------------------------------------------------
648
649    fn hash_bytes(bytes: &[u8]) -> u64 {
650        let mut h = DefaultHasher::new();
651        h.write(bytes);
652        h.finish()
653    }
654
655    fn hash_u64(word: u64) -> u64 {
656        let mut h = DefaultHasher::new();
657        h.write_u64(word);
658        h.finish()
659    }
660
661    #[test]
662    fn hasher_new_and_default_agree_and_are_deterministic() {
663        assert_eq!(DefaultHasher::new().finish(), DefaultHasher::new().finish());
664        assert_eq!(
665            DefaultHasher::new().finish(),
666            DefaultHasher::default().finish()
667        );
668    }
669
670    #[test]
671    fn hasher_is_deterministic_within_a_run() {
672        assert_eq!(hash_bytes(b"azul"), hash_bytes(b"azul"));
673        assert_eq!(
674            hash_u64(0xDEAD_BEEF_CAFE_F00D),
675            hash_u64(0xDEAD_BEEF_CAFE_F00D)
676        );
677    }
678
679    #[test]
680    fn hasher_distinguishes_different_inputs() {
681        assert_ne!(hash_bytes(b"a"), hash_bytes(b"b"));
682        assert_ne!(hash_u64(0), hash_u64(1));
683    }
684
685    #[test]
686    fn hasher_is_order_sensitive() {
687        let mut a = DefaultHasher::new();
688        a.write_u64(1);
689        a.write_u64(2);
690
691        let mut b = DefaultHasher::new();
692        b.write_u64(2);
693        b.write_u64(1);
694
695        assert_ne!(a.finish(), b.finish());
696    }
697
698    #[test]
699    fn hasher_finish_does_not_consume_state() {
700        let mut h = DefaultHasher::new();
701        h.write_u64(7);
702        let first = h.finish();
703        // `finish` must be a pure read: calling it twice returns the same value.
704        assert_eq!(first, h.finish());
705        // ...and further writes must keep mutating the same running state.
706        h.write_u64(7);
707        assert_ne!(first, h.finish());
708    }
709
710    // ---------------------------------------------------------------
711    // DefaultHasher — numeric limits / overflow (exercises the private `add`
712    // via its 1:1 forwarders `write_u64` / `write_usize` / `write_u8`)
713    // ---------------------------------------------------------------
714
715    #[test]
716    fn hasher_handles_integer_limits_without_panicking() {
717        // `add` does a `wrapping_mul`; a debug build must not overflow-panic.
718        for word in [
719            0u64,
720            1,
721            u64::MAX,
722            u64::MAX - 1,
723            i64::MIN as u64, // 0x8000_0000_0000_0000 — "negative" bit pattern
724            i64::MAX as u64,
725            -1i64 as u64,
726            1 << 63,
727            usize::MAX as u64,
728        ] {
729            let h = hash_u64(word);
730            // deterministic + no panic; value itself is impl-defined
731            assert_eq!(h, hash_u64(word));
732        }
733
734        let mut h = DefaultHasher::new();
735        h.write_usize(usize::MAX);
736        h.write_usize(0);
737        h.write_u8(u8::MAX);
738        h.write_u8(0);
739        let _ = h.finish();
740    }
741
742    #[test]
743    fn hasher_repeated_max_words_do_not_overflow_panic() {
744        // Hammer the wrapping rotate/xor/multiply chain: every iteration
745        // overflows u64. Must wrap, never panic (even in a debug profile).
746        let mut h = DefaultHasher::new();
747        for _ in 0..10_000 {
748            h.write_u64(u64::MAX);
749        }
750        let a = h.finish();
751
752        let mut h2 = DefaultHasher::new();
753        for _ in 0..10_000 {
754            h2.write_u64(u64::MAX);
755        }
756        assert_eq!(a, h2.finish(), "overflowing chain must stay deterministic");
757    }
758
759    #[test]
760    fn hasher_zero_words_are_deterministic() {
761        let mut h = DefaultHasher::new();
762        for _ in 0..1_000 {
763            h.write_u64(0);
764        }
765        let a = h.finish();
766
767        let mut h2 = DefaultHasher::new();
768        for _ in 0..1_000 {
769            h2.write_u64(0);
770        }
771        assert_eq!(a, h2.finish());
772    }
773
774    // ---------------------------------------------------------------
775    // DefaultHasher — `write` chunking / boundaries / unicode
776    // ---------------------------------------------------------------
777
778    #[test]
779    fn hasher_write_empty_slice_does_not_panic() {
780        let mut h = DefaultHasher::new();
781        h.write(&[]);
782        h.write(&[]);
783        let a = h.finish();
784
785        let mut h2 = DefaultHasher::new();
786        h2.write(&[]);
787        h2.write(&[]);
788        assert_eq!(a, h2.finish());
789    }
790
791    #[test]
792    fn hasher_write_covers_every_chunk_boundary() {
793        // The `no_std` impl walks `chunks(8)` and zero-pads the tail; lengths
794        // 0..=24 cover empty, short, exact-multiple and ragged-tail cases.
795        let data: Vec<u8> = (0u8..=24).collect();
796        for len in 0..=24usize {
797            let slice = &data[..len];
798            assert_eq!(hash_bytes(slice), hash_bytes(slice), "len {len}");
799        }
800        // A short slice must not collide with the same slice explicitly padded
801        // out past the next 8-byte chunk boundary.
802        assert_ne!(
803            hash_bytes(&[1u8]),
804            hash_bytes(&[1u8, 0, 0, 0, 0, 0, 0, 0, 0])
805        );
806    }
807
808    // `write` must not swallow a trailing zero byte: `[1]` and `[1, 0]` are
809    // different inputs and must hash differently.
810    //
811    // The `no_std` shim FAILS this: it zero-pads the final `chunks(8)` chunk
812    // and mixes in no length, so `[1]` and `[1, 0]` both become the word
813    // `0x0000_0000_0000_0001` — a guaranteed collision for every pair of byte
814    // strings differing only in trailing zeros. Kept as a live assertion for
815    // the (default) `std` build and `ignore`d rather than weakened under
816    // `no_std`; see the autotest report.
817    #[cfg_attr(
818        not(feature = "std"),
819        ignore = "no_std DefaultHasher zero-pads without length mixing: hash([1]) == hash([1, 0])"
820    )]
821    #[test]
822    fn hasher_write_does_not_swallow_trailing_zero_bytes() {
823        assert_ne!(hash_bytes(&[1u8]), hash_bytes(&[1u8, 0]));
824        assert_ne!(hash_bytes(b"az"), hash_bytes(b"az\0"));
825        assert_ne!(hash_bytes(&[]), hash_bytes(&[0u8]));
826    }
827
828    #[test]
829    fn hasher_handles_huge_input() {
830        let big: Vec<u8> = (0..(1 << 16)).map(|i| (i % 251) as u8).collect();
831        let a = hash_bytes(&big);
832        assert_eq!(a, hash_bytes(&big));
833
834        // A single flipped byte in the middle must change the digest.
835        let mut flipped = big.clone();
836        flipped[1 << 15] ^= 0xFF;
837        assert_ne!(a, hash_bytes(&flipped));
838    }
839
840    #[test]
841    fn hasher_handles_unicode_and_nul_bytes() {
842        for s in [
843            "",
844            "\u{0}",
845            "ascii",
846            "héllo wörld",
847            "日本語テキスト",
848            "🦀🔥👨‍👩‍👧‍👦",
849            "a\u{0}b",
850            "\u{FEFF}bom",
851            "\u{10FFFF}",
852        ] {
853            let mut h = DefaultHasher::new();
854            s.hash(&mut h);
855            let a = h.finish();
856
857            let mut h2 = DefaultHasher::new();
858            s.hash(&mut h2);
859            assert_eq!(a, h2.finish(), "unstable hash for {s:?}");
860        }
861
862        // Interior NUL must not truncate the input (C-string style bug).
863        let mut a = DefaultHasher::new();
864        "a\u{0}b".hash(&mut a);
865        let mut b = DefaultHasher::new();
866        "a".hash(&mut b);
867        assert_ne!(a.finish(), b.finish());
868    }
869
870    #[test]
871    fn hasher_respects_eq_hash_contract_for_std_types() {
872        fn digest<T: Hash>(t: &T) -> u64 {
873            let mut h = DefaultHasher::new();
874            t.hash(&mut h);
875            h.finish()
876        }
877
878        // Equal values must hash equal.
879        assert_eq!(digest(&String::from("x")), digest(&String::from("x")));
880        assert_eq!(
881            digest(&alloc::vec![1u64, 2, 3]),
882            digest(&alloc::vec![1u64, 2, 3])
883        );
884        assert_eq!(digest(&(1u8, "a")), digest(&(1u8, "a")));
885
886        // Length must be part of the digest: [1,2] vs [1,2,0] must differ...
887        assert_ne!(
888            digest(&alloc::vec![1u8, 2]),
889            digest(&alloc::vec![1u8, 2, 0])
890        );
891        // ...and prefix-concatenation must not collide ("ab" vs "a"+"b" fields).
892        assert_ne!(digest(&("ab", "")), digest(&("a", "b")));
893    }
894
895    // ---------------------------------------------------------------
896    // `no_std` shim internals: exact FxHasher-style formula of the private
897    // `add`, reached through its 1:1 forwarder `write_u64`.
898    // ---------------------------------------------------------------
899
900    #[cfg(not(feature = "std"))]
901    #[test]
902    fn nostd_hasher_add_matches_documented_formula() {
903        const SEED: u64 = 0x51_7c_c1_b7_27_22_0a_95;
904        const ROTATE: u32 = 5;
905
906        fn expect(words: &[u64]) -> u64 {
907            words
908                .iter()
909                .fold(0u64, |h, w| (h.rotate_left(ROTATE) ^ w).wrapping_mul(SEED))
910        }
911
912        assert_eq!(DefaultHasher::new().finish(), 0, "fresh state must be 0");
913
914        for words in [
915            &[0u64][..],
916            &[1][..],
917            &[u64::MAX][..],
918            &[i64::MIN as u64][..],
919            &[u64::MAX, u64::MAX, u64::MAX][..],
920            &[0, u64::MAX, 0, 1 << 63][..],
921        ] {
922            let mut h = DefaultHasher::new();
923            for w in words {
924                h.write_u64(*w);
925            }
926            assert_eq!(h.finish(), expect(words), "formula drift for {words:?}");
927        }
928    }
929
930    #[cfg(not(feature = "std"))]
931    #[test]
932    fn nostd_hasher_zero_is_an_absorbing_state() {
933        // Documented FxHasher weakness, asserted so it stays *intentional*:
934        // from a zero state, hashing zero words keeps the state at zero
935        // ((0.rotate_left(5) ^ 0) * SEED == 0).
936        let mut h = DefaultHasher::new();
937        for _ in 0..64 {
938            h.write_u64(0);
939        }
940        assert_eq!(h.finish(), 0);
941
942        // Leading zero words are therefore invisible: hash([0, x]) == hash([x]).
943        let mut a = DefaultHasher::new();
944        a.write_u64(0);
945        a.write_u64(0xABCD);
946        let mut b = DefaultHasher::new();
947        b.write_u64(0xABCD);
948        assert_eq!(a.finish(), b.finish());
949    }
950
951    #[cfg(not(feature = "std"))]
952    #[test]
953    fn nostd_hasher_write_empty_slice_is_a_noop() {
954        // `chunks(8)` over an empty slice yields nothing, so the state is untouched.
955        let mut h = DefaultHasher::new();
956        h.write(b"seed");
957        let before = h.finish();
958        h.write(&[]);
959        assert_eq!(h.finish(), before);
960    }
961
962    // ---------------------------------------------------------------
963    // Public type aliases — ordering / dedup invariants
964    // ---------------------------------------------------------------
965
966    #[test]
967    fn ordered_map_iterates_in_key_order() {
968        let mut m: OrderedMap<i64, &str> = OrderedMap::new();
969        for (k, v) in [
970            (i64::MAX, "max"),
971            (0, "zero"),
972            (i64::MIN, "min"),
973            (-1, "neg"),
974        ] {
975            let _ = m.insert(k, v);
976        }
977        let keys: Vec<i64> = m.keys().copied().collect();
978        assert_eq!(keys, alloc::vec![i64::MIN, -1, 0, i64::MAX]);
979
980        // Re-insert must overwrite, not duplicate.
981        assert_eq!(m.insert(0, "zero2"), Some("zero"));
982        assert_eq!(m.len(), 4);
983        assert_eq!(m.get(&0).copied(), Some("zero2"));
984    }
985
986    #[test]
987    fn fast_btree_set_dedups_and_orders() {
988        let mut s: FastBTreeSet<u32> = FastBTreeSet::new();
989        assert!(s.insert(u32::MAX));
990        assert!(s.insert(0));
991        assert!(!s.insert(0), "duplicate insert must report false");
992        assert!(s.insert(1));
993
994        assert_eq!(s.len(), 3);
995        assert_eq!(
996            s.iter().copied().collect::<Vec<u32>>(),
997            alloc::vec![0, 1, u32::MAX]
998        );
999        assert!(s.contains(&u32::MAX));
1000        assert!(!s.contains(&2));
1001    }
1002}