Skip to main content

noyalib/
anchors.rs

1//! Smart pointer anchor types for shared/DAG structures.
2//!
3//! These wrappers provide anchor semantics for `Rc` and `Arc` pointers,
4//! allowing YAML serialization of shared data structures.
5
6// SPDX-License-Identifier: MIT OR Apache-2.0
7// Copyright (c) 2026 Noyalib. All rights reserved.
8
9use crate::prelude::*;
10use core::ops::Deref;
11
12#[cfg(not(feature = "std"))]
13use alloc::rc::{Rc, Weak as RcWeak};
14#[cfg(not(feature = "std"))]
15use alloc::sync::Weak as ArcWeak;
16#[cfg(feature = "std")]
17use std::rc::{Rc, Weak as RcWeak};
18#[cfg(feature = "std")]
19use std::sync::Weak as ArcWeak;
20
21use crate::prelude::FxHashMap;
22
23/// Thread-local identity tracking for automatic anchor/alias emission.
24///
25/// Activated by `to_string_tracking_shared` (and writer variants). When active,
26/// `RcAnchor`/`ArcAnchor`/`*WeakAnchor` consult this state during serialization:
27/// the first time a pointer is seen, they emit a YAML anchor; subsequent
28/// sightings emit an alias.
29///
30/// Not re-entrant across threads. `ArcAnchor` serialization remains on the
31/// serialising thread; the scope guard ensures state does not leak across calls.
32#[cfg(feature = "std")]
33pub(crate) mod shared_tracking {
34    use crate::prelude::FxHashMap;
35    use core::cell::RefCell;
36
37    pub(crate) enum TrackOutcome {
38        NotActive,
39        New(u32),
40        Existing(u32),
41    }
42
43    struct AnchorState {
44        seen: FxHashMap<usize, u32>,
45        next_id: u32,
46    }
47
48    impl AnchorState {
49        fn new() -> Self {
50            Self {
51                seen: FxHashMap::default(),
52                next_id: 1,
53            }
54        }
55    }
56
57    std::thread_local! {
58        static STATE: RefCell<Option<AnchorState>> = const { RefCell::new(None) };
59    }
60
61    /// RAII guard that installs a fresh tracking state on construction and
62    /// clears it on drop. Nested scopes are rejected (only the outermost scope
63    /// is authoritative) — this prevents accidental state bleed when users
64    /// compose serializers.
65    pub(crate) struct AnchorScope {
66        owns: bool,
67    }
68
69    impl AnchorScope {
70        pub(crate) fn enter() -> Self {
71            let owns = STATE.with(|s| {
72                let mut borrow = s.borrow_mut();
73                if borrow.is_none() {
74                    *borrow = Some(AnchorState::new());
75                    true
76                } else {
77                    false
78                }
79            });
80            Self { owns }
81        }
82    }
83
84    impl Drop for AnchorScope {
85        fn drop(&mut self) {
86            if self.owns {
87                STATE.with(|s| {
88                    *s.borrow_mut() = None;
89                });
90            }
91        }
92    }
93
94    /// Record a pointer; return whether it is newly seen or already tracked.
95    pub(crate) fn track(ptr: usize) -> TrackOutcome {
96        STATE.with(|s| {
97            let mut borrow = s.borrow_mut();
98            match borrow.as_mut() {
99                None => TrackOutcome::NotActive,
100                Some(state) => {
101                    if let Some(&id) = state.seen.get(&ptr) {
102                        TrackOutcome::Existing(id)
103                    } else {
104                        let id = state.next_id;
105                        state.next_id = state.next_id.saturating_add(1);
106                        let _ = state.seen.insert(ptr, id);
107                        TrackOutcome::New(id)
108                    }
109                }
110            }
111        })
112    }
113
114    /// Look up without inserting. Used by weak-ref serializers: emit an alias
115    /// only if the target was already anchored by a strong reference.
116    pub(crate) fn peek(ptr: usize) -> Option<u32> {
117        STATE.with(|s| {
118            s.borrow()
119                .as_ref()
120                .and_then(|state| state.seen.get(&ptr).copied())
121        })
122    }
123
124    pub(crate) fn format_id(id: u32) -> String {
125        format!("id{id:03}")
126    }
127}
128
129/// An `Rc` wrapper with YAML anchor semantics.
130///
131/// Serializes by delegating to the inner `T`. Deserializes by wrapping the
132/// result in `Rc`.
133///
134/// # Examples
135///
136/// ```
137/// use noyalib::RcAnchor;
138/// let a: RcAnchor<String> = RcAnchor::from("shared".to_string());
139/// assert_eq!(&*a, "shared");
140/// ```
141#[derive(Clone)]
142pub struct RcAnchor<T>(pub Rc<T>);
143
144impl<T: fmt::Debug> fmt::Debug for RcAnchor<T> {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        f.debug_tuple("RcAnchor").field(&self.0).finish()
147    }
148}
149
150impl<T> Deref for RcAnchor<T> {
151    type Target = T;
152    fn deref(&self) -> &T {
153        &self.0
154    }
155}
156
157impl<T> From<T> for RcAnchor<T> {
158    fn from(v: T) -> Self {
159        Self(Rc::new(v))
160    }
161}
162
163impl<T> From<Rc<T>> for RcAnchor<T> {
164    fn from(v: Rc<T>) -> Self {
165        Self(v)
166    }
167}
168
169impl<T> RcAnchor<T> {
170    /// Unwrap into the inner `Rc`.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// use noyalib::RcAnchor;
176    /// let a: RcAnchor<i32> = RcAnchor::from(7);
177    /// let inner = a.into_inner();
178    /// assert_eq!(*inner, 7);
179    /// ```
180    #[must_use]
181    pub fn into_inner(self) -> Rc<T> {
182        self.0
183    }
184}
185
186impl<T: serde_core::Serialize> serde_core::Serialize for RcAnchor<T> {
187    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
188    where
189        S: serde_core::Serializer,
190    {
191        #[cfg(feature = "std")]
192        {
193            let ptr = Rc::as_ptr(&self.0).cast::<()>() as usize;
194            match shared_tracking::track(ptr) {
195                shared_tracking::TrackOutcome::NotActive => self.0.serialize(serializer),
196                shared_tracking::TrackOutcome::New(id) => {
197                    let id_str = shared_tracking::format_id(id);
198                    serializer
199                        .serialize_newtype_struct(crate::fmt::MAGIC_ANCHOR_DEF, &(id_str, &*self.0))
200                }
201                shared_tracking::TrackOutcome::Existing(id) => {
202                    let id_str = shared_tracking::format_id(id);
203                    serializer.serialize_newtype_struct(crate::fmt::MAGIC_ANCHOR_REF, &id_str)
204                }
205            }
206        }
207        #[cfg(not(feature = "std"))]
208        {
209            self.0.serialize(serializer)
210        }
211    }
212}
213
214impl<'de, T: serde_core::Deserialize<'de>> serde_core::Deserialize<'de> for RcAnchor<T> {
215    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
216    where
217        D: serde_core::Deserializer<'de>,
218    {
219        T::deserialize(deserializer).map(|v| Self(Rc::new(v)))
220    }
221}
222
223/// An `Arc` wrapper with YAML anchor semantics.
224///
225/// Serializes by delegating to the inner `T`. Deserializes by wrapping the
226/// result in `Arc`.
227///
228/// # Examples
229///
230/// ```
231/// use noyalib::ArcAnchor;
232/// let a: ArcAnchor<String> = ArcAnchor::from("shared".to_string());
233/// assert_eq!(&*a, "shared");
234/// ```
235#[derive(Clone)]
236pub struct ArcAnchor<T>(pub Arc<T>);
237
238impl<T: fmt::Debug> fmt::Debug for ArcAnchor<T> {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        f.debug_tuple("ArcAnchor").field(&self.0).finish()
241    }
242}
243
244impl<T> Deref for ArcAnchor<T> {
245    type Target = T;
246    fn deref(&self) -> &T {
247        &self.0
248    }
249}
250
251impl<T> From<T> for ArcAnchor<T> {
252    fn from(v: T) -> Self {
253        Self(Arc::new(v))
254    }
255}
256
257impl<T> From<Arc<T>> for ArcAnchor<T> {
258    fn from(v: Arc<T>) -> Self {
259        Self(v)
260    }
261}
262
263impl<T> ArcAnchor<T> {
264    /// Unwrap into the inner `Arc`.
265    ///
266    /// # Examples
267    ///
268    /// ```
269    /// use noyalib::ArcAnchor;
270    /// let a: ArcAnchor<i32> = ArcAnchor::from(7);
271    /// let inner = a.into_inner();
272    /// assert_eq!(*inner, 7);
273    /// ```
274    #[must_use]
275    pub fn into_inner(self) -> Arc<T> {
276        self.0
277    }
278}
279
280impl<T: serde_core::Serialize> serde_core::Serialize for ArcAnchor<T> {
281    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
282    where
283        S: serde_core::Serializer,
284    {
285        #[cfg(feature = "std")]
286        {
287            let ptr = Arc::as_ptr(&self.0).cast::<()>() as usize;
288            match shared_tracking::track(ptr) {
289                shared_tracking::TrackOutcome::NotActive => self.0.serialize(serializer),
290                shared_tracking::TrackOutcome::New(id) => {
291                    let id_str = shared_tracking::format_id(id);
292                    serializer
293                        .serialize_newtype_struct(crate::fmt::MAGIC_ANCHOR_DEF, &(id_str, &*self.0))
294                }
295                shared_tracking::TrackOutcome::Existing(id) => {
296                    let id_str = shared_tracking::format_id(id);
297                    serializer.serialize_newtype_struct(crate::fmt::MAGIC_ANCHOR_REF, &id_str)
298                }
299            }
300        }
301        #[cfg(not(feature = "std"))]
302        {
303            self.0.serialize(serializer)
304        }
305    }
306}
307
308impl<'de, T: serde_core::Deserialize<'de>> serde_core::Deserialize<'de> for ArcAnchor<T> {
309    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
310    where
311        D: serde_core::Deserializer<'de>,
312    {
313        T::deserialize(deserializer).map(|v| Self(Arc::new(v)))
314    }
315}
316
317/// A weak `Rc` reference with YAML anchor semantics.
318///
319/// Serializes as `null` if the reference is dangling, otherwise serializes
320/// the inner value. Deserialization from `null` produces a dangling weak ref.
321///
322/// # Examples
323///
324/// ```
325/// use noyalib::RcWeakAnchor;
326/// let w: RcWeakAnchor<i32> = RcWeakAnchor::dangling();
327/// assert!(w.upgrade().is_none());
328/// ```
329#[derive(Clone)]
330pub struct RcWeakAnchor<T>(pub RcWeak<T>);
331
332impl<T: fmt::Debug> fmt::Debug for RcWeakAnchor<T> {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        match self.0.upgrade() {
335            Some(v) => f.debug_tuple("RcWeakAnchor").field(&v).finish(),
336            None => f.debug_tuple("RcWeakAnchor").field(&"(dangling)").finish(),
337        }
338    }
339}
340
341impl<T> RcWeakAnchor<T> {
342    /// Create a dangling weak anchor.
343    ///
344    /// # Examples
345    ///
346    /// ```
347    /// use noyalib::RcWeakAnchor;
348    /// let w: RcWeakAnchor<String> = RcWeakAnchor::dangling();
349    /// assert!(w.upgrade().is_none());
350    /// ```
351    #[must_use]
352    pub fn dangling() -> Self {
353        Self(RcWeak::new())
354    }
355
356    /// Unwrap into the inner `Weak`.
357    ///
358    /// # Examples
359    ///
360    /// ```
361    /// use noyalib::RcWeakAnchor;
362    /// let w: RcWeakAnchor<i32> = RcWeakAnchor::dangling();
363    /// let inner = w.into_inner();
364    /// assert!(inner.upgrade().is_none());
365    /// ```
366    #[must_use]
367    pub fn into_inner(self) -> RcWeak<T> {
368        self.0
369    }
370
371    /// Attempt to upgrade to a strong `Rc`.
372    ///
373    /// # Examples
374    ///
375    /// ```
376    /// use noyalib::RcWeakAnchor;
377    /// let w: RcWeakAnchor<i32> = RcWeakAnchor::dangling();
378    /// assert!(w.upgrade().is_none());
379    /// ```
380    #[must_use]
381    pub fn upgrade(&self) -> Option<Rc<T>> {
382        self.0.upgrade()
383    }
384}
385
386impl<T> From<RcWeak<T>> for RcWeakAnchor<T> {
387    fn from(v: RcWeak<T>) -> Self {
388        Self(v)
389    }
390}
391
392impl<T: serde_core::Serialize> serde_core::Serialize for RcWeakAnchor<T> {
393    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
394    where
395        S: serde_core::Serializer,
396    {
397        match self.0.upgrade() {
398            Some(v) => {
399                #[cfg(feature = "std")]
400                {
401                    // Weak refs never define a new anchor. If tracking is active
402                    // and the target was already anchored by a strong reference,
403                    // emit an alias; otherwise fall back to inline value.
404                    let ptr = Rc::as_ptr(&v).cast::<()>() as usize;
405                    if let Some(id) = shared_tracking::peek(ptr) {
406                        let id_str = shared_tracking::format_id(id);
407                        return serializer
408                            .serialize_newtype_struct(crate::fmt::MAGIC_ANCHOR_REF, &id_str);
409                    }
410                }
411                v.serialize(serializer)
412            }
413            None => serializer.serialize_none(),
414        }
415    }
416}
417
418impl<'de, T> serde_core::Deserialize<'de> for RcWeakAnchor<T> {
419    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
420    where
421        D: serde_core::Deserializer<'de>,
422    {
423        // Always deserialize as a dangling weak — there's no registry to look up.
424        // We consume the value to avoid errors.
425        let _ = serde_core::de::IgnoredAny::deserialize(deserializer)?;
426        Ok(Self(RcWeak::new()))
427    }
428}
429
430/// A weak `Arc` reference with YAML anchor semantics.
431///
432/// Serializes as `null` if the reference is dangling, otherwise serializes
433/// the inner value. Deserialization from `null` produces a dangling weak ref.
434///
435/// # Examples
436///
437/// ```
438/// use noyalib::ArcWeakAnchor;
439/// let w: ArcWeakAnchor<i32> = ArcWeakAnchor::dangling();
440/// assert!(w.upgrade().is_none());
441/// ```
442#[derive(Clone)]
443pub struct ArcWeakAnchor<T>(pub ArcWeak<T>);
444
445impl<T: fmt::Debug> fmt::Debug for ArcWeakAnchor<T> {
446    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447        match self.0.upgrade() {
448            Some(v) => f.debug_tuple("ArcWeakAnchor").field(&v).finish(),
449            None => f.debug_tuple("ArcWeakAnchor").field(&"(dangling)").finish(),
450        }
451    }
452}
453
454impl<T> ArcWeakAnchor<T> {
455    /// Create a dangling weak anchor.
456    ///
457    /// # Examples
458    ///
459    /// ```
460    /// use noyalib::ArcWeakAnchor;
461    /// let w: ArcWeakAnchor<String> = ArcWeakAnchor::dangling();
462    /// assert!(w.upgrade().is_none());
463    /// ```
464    #[must_use]
465    pub fn dangling() -> Self {
466        Self(ArcWeak::new())
467    }
468
469    /// Unwrap into the inner `Weak`.
470    ///
471    /// # Examples
472    ///
473    /// ```
474    /// use noyalib::ArcWeakAnchor;
475    /// let w: ArcWeakAnchor<i32> = ArcWeakAnchor::dangling();
476    /// let inner = w.into_inner();
477    /// assert!(inner.upgrade().is_none());
478    /// ```
479    #[must_use]
480    pub fn into_inner(self) -> ArcWeak<T> {
481        self.0
482    }
483
484    /// Attempt to upgrade to a strong `Arc`.
485    ///
486    /// # Examples
487    ///
488    /// ```
489    /// use noyalib::ArcWeakAnchor;
490    /// let w: ArcWeakAnchor<i32> = ArcWeakAnchor::dangling();
491    /// assert!(w.upgrade().is_none());
492    /// ```
493    #[must_use]
494    pub fn upgrade(&self) -> Option<Arc<T>> {
495        self.0.upgrade()
496    }
497}
498
499impl<T> From<ArcWeak<T>> for ArcWeakAnchor<T> {
500    fn from(v: ArcWeak<T>) -> Self {
501        Self(v)
502    }
503}
504
505impl<T: serde_core::Serialize> serde_core::Serialize for ArcWeakAnchor<T> {
506    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
507    where
508        S: serde_core::Serializer,
509    {
510        match self.0.upgrade() {
511            Some(v) => {
512                #[cfg(feature = "std")]
513                {
514                    let ptr = Arc::as_ptr(&v).cast::<()>() as usize;
515                    if let Some(id) = shared_tracking::peek(ptr) {
516                        let id_str = shared_tracking::format_id(id);
517                        return serializer
518                            .serialize_newtype_struct(crate::fmt::MAGIC_ANCHOR_REF, &id_str);
519                    }
520                }
521                v.serialize(serializer)
522            }
523            None => serializer.serialize_none(),
524        }
525    }
526}
527
528impl<'de, T> serde_core::Deserialize<'de> for ArcWeakAnchor<T> {
529    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
530    where
531        D: serde_core::Deserializer<'de>,
532    {
533        let _ = serde_core::de::IgnoredAny::deserialize(deserializer)?;
534        Ok(Self(ArcWeak::new()))
535    }
536}
537
538// ── Anchor Registries ──────────────────────────────────────────────────
539
540/// Registry for shared `Rc` anchor references during deserialization.
541///
542/// When the same YAML anchor is referenced multiple times, all aliases
543/// point to the same heap allocation via `Rc::clone`. This enables
544/// true shared-memory DAG structures rather than duplicated subtrees.
545///
546/// # Examples
547///
548/// ```rust
549/// use noyalib::AnchorRegistry;
550/// use std::rc::Rc;
551///
552/// let mut reg = AnchorRegistry::<String>::new();
553/// let rc = reg.register("shared".into(), "hello".into());
554/// let alias = reg.resolve("shared").unwrap();
555/// assert!(Rc::ptr_eq(&rc, &alias));
556/// ```
557pub struct AnchorRegistry<T> {
558    anchors: FxHashMap<String, Rc<T>>,
559}
560
561impl<T: fmt::Debug> fmt::Debug for AnchorRegistry<T> {
562    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563        f.debug_struct("AnchorRegistry")
564            .field("len", &self.anchors.len())
565            .finish()
566    }
567}
568
569impl<T> Default for AnchorRegistry<T> {
570    fn default() -> Self {
571        Self::new()
572    }
573}
574
575impl<T> AnchorRegistry<T> {
576    /// Create an empty registry.
577    ///
578    /// # Examples
579    ///
580    /// ```
581    /// use noyalib::AnchorRegistry;
582    /// let reg = AnchorRegistry::<String>::new();
583    /// assert!(reg.is_empty());
584    /// ```
585    #[must_use]
586    pub fn new() -> Self {
587        Self {
588            anchors: FxHashMap::default(),
589        }
590    }
591
592    /// Register a value under the given anchor name.
593    ///
594    /// Returns the `Rc` wrapping the value. If an anchor with the
595    /// same name already existed, the old entry is replaced.
596    ///
597    /// # Examples
598    ///
599    /// ```
600    /// use noyalib::AnchorRegistry;
601    /// let mut reg = AnchorRegistry::<i32>::new();
602    /// let rc = reg.register("n".into(), 7);
603    /// assert_eq!(*rc, 7);
604    /// ```
605    pub fn register(&mut self, name: String, value: T) -> Rc<T> {
606        let rc = Rc::new(value);
607        let _ = self.anchors.insert(name, Rc::clone(&rc));
608        rc
609    }
610
611    /// Resolve an anchor by name, returning a cloned `Rc` if present.
612    ///
613    /// # Examples
614    ///
615    /// ```
616    /// use noyalib::AnchorRegistry;
617    /// let mut reg = AnchorRegistry::<i32>::new();
618    /// let _ = reg.register("a".into(), 1);
619    /// assert_eq!(*reg.resolve("a").unwrap(), 1);
620    /// assert!(reg.resolve("missing").is_none());
621    /// ```
622    #[must_use]
623    pub fn resolve(&self, name: &str) -> Option<Rc<T>> {
624        self.anchors.get(name).cloned()
625    }
626
627    /// Returns the number of registered anchors.
628    ///
629    /// # Examples
630    ///
631    /// ```
632    /// use noyalib::AnchorRegistry;
633    /// let mut reg = AnchorRegistry::<i32>::new();
634    /// let _ = reg.register("a".into(), 1);
635    /// assert_eq!(reg.len(), 1);
636    /// ```
637    #[must_use]
638    pub fn len(&self) -> usize {
639        self.anchors.len()
640    }
641
642    /// Returns `true` if no anchors are registered.
643    ///
644    /// # Examples
645    ///
646    /// ```
647    /// use noyalib::AnchorRegistry;
648    /// let reg = AnchorRegistry::<i32>::new();
649    /// assert!(reg.is_empty());
650    /// ```
651    #[must_use]
652    pub fn is_empty(&self) -> bool {
653        self.anchors.is_empty()
654    }
655
656    /// Remove all entries from the registry.
657    ///
658    /// # Examples
659    ///
660    /// ```
661    /// use noyalib::AnchorRegistry;
662    /// let mut reg = AnchorRegistry::<i32>::new();
663    /// let _ = reg.register("a".into(), 1);
664    /// reg.clear();
665    /// assert!(reg.is_empty());
666    /// ```
667    pub fn clear(&mut self) {
668        self.anchors.clear();
669    }
670}
671
672/// Registry for shared `Arc` anchor references during deserialization.
673///
674/// Thread-safe counterpart to [`AnchorRegistry`]. All aliases for the
675/// same anchor share one `Arc` allocation, enabling cross-thread DAGs.
676///
677/// # Examples
678///
679/// ```rust
680/// use noyalib::ArcAnchorRegistry;
681/// use std::sync::Arc;
682///
683/// let mut reg = ArcAnchorRegistry::<String>::new();
684/// let arc = reg.register("shared".into(), "hello".into());
685/// let alias = reg.resolve("shared").unwrap();
686/// assert!(Arc::ptr_eq(&arc, &alias));
687/// ```
688pub struct ArcAnchorRegistry<T> {
689    anchors: FxHashMap<String, Arc<T>>,
690}
691
692impl<T: fmt::Debug> fmt::Debug for ArcAnchorRegistry<T> {
693    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
694        f.debug_struct("ArcAnchorRegistry")
695            .field("len", &self.anchors.len())
696            .finish()
697    }
698}
699
700impl<T> Default for ArcAnchorRegistry<T> {
701    fn default() -> Self {
702        Self::new()
703    }
704}
705
706impl<T> ArcAnchorRegistry<T> {
707    /// Create an empty registry.
708    ///
709    /// # Examples
710    ///
711    /// ```
712    /// use noyalib::ArcAnchorRegistry;
713    /// let reg = ArcAnchorRegistry::<String>::new();
714    /// assert!(reg.is_empty());
715    /// ```
716    #[must_use]
717    pub fn new() -> Self {
718        Self {
719            anchors: FxHashMap::default(),
720        }
721    }
722
723    /// Register a value under the given anchor name.
724    ///
725    /// Returns the `Arc` wrapping the value.
726    ///
727    /// # Examples
728    ///
729    /// ```
730    /// use noyalib::ArcAnchorRegistry;
731    /// let mut reg = ArcAnchorRegistry::<i32>::new();
732    /// let arc = reg.register("n".into(), 7);
733    /// assert_eq!(*arc, 7);
734    /// ```
735    pub fn register(&mut self, name: String, value: T) -> Arc<T> {
736        let arc = Arc::new(value);
737        let _ = self.anchors.insert(name, Arc::clone(&arc));
738        arc
739    }
740
741    /// Resolve an anchor by name, returning a cloned `Arc` if present.
742    ///
743    /// # Examples
744    ///
745    /// ```
746    /// use noyalib::ArcAnchorRegistry;
747    /// let mut reg = ArcAnchorRegistry::<i32>::new();
748    /// let _ = reg.register("a".into(), 1);
749    /// assert_eq!(*reg.resolve("a").unwrap(), 1);
750    /// ```
751    #[must_use]
752    pub fn resolve(&self, name: &str) -> Option<Arc<T>> {
753        self.anchors.get(name).cloned()
754    }
755
756    /// Returns the number of registered anchors.
757    ///
758    /// # Examples
759    ///
760    /// ```
761    /// use noyalib::ArcAnchorRegistry;
762    /// let reg = ArcAnchorRegistry::<i32>::new();
763    /// assert_eq!(reg.len(), 0);
764    /// ```
765    #[must_use]
766    pub fn len(&self) -> usize {
767        self.anchors.len()
768    }
769
770    /// Returns `true` if no anchors are registered.
771    ///
772    /// # Examples
773    ///
774    /// ```
775    /// use noyalib::ArcAnchorRegistry;
776    /// let reg = ArcAnchorRegistry::<i32>::new();
777    /// assert!(reg.is_empty());
778    /// ```
779    #[must_use]
780    pub fn is_empty(&self) -> bool {
781        self.anchors.is_empty()
782    }
783
784    /// Remove all entries from the registry.
785    ///
786    /// # Examples
787    ///
788    /// ```
789    /// use noyalib::ArcAnchorRegistry;
790    /// let mut reg = ArcAnchorRegistry::<i32>::new();
791    /// let _ = reg.register("a".into(), 1);
792    /// reg.clear();
793    /// assert!(reg.is_empty());
794    /// ```
795    pub fn clear(&mut self) {
796        self.anchors.clear();
797    }
798}
799
800// ════════════════════════════════════════════════════════════════
801// Issue #5 — recursive anchor types for cyclic YAML graphs.
802// ════════════════════════════════════════════════════════════════
803
804#[cfg(feature = "std")]
805use std::cell::RefCell;
806#[cfg(feature = "std")]
807use std::sync::{Arc, Mutex};
808
809/// Single-threaded recursive anchor type for cyclic / late-initialised
810/// YAML graphs.
811///
812/// Wraps `Rc<RefCell<Option<T>>>` so a value can be referenced by an
813/// alias *before* the anchor is fully populated — the canonical
814/// shape for self-referential YAML configs (call graphs, scene
815/// trees, doubly-linked structures emitted as anchor + alias).
816///
817/// Access through [`RcRecursive::borrow`] / [`RcRecursive::borrow_mut`]
818/// (not `Deref`) so the interior mutability is always explicit at
819/// the call site — borrow-checker complaints surface in the YAML
820/// code, not in the surrounding logic.
821///
822/// For thread-safe variants see [`ArcRecursive`].
823///
824/// # Examples
825///
826/// ```
827/// use noyalib::RcRecursive;
828/// let r: RcRecursive<String> = RcRecursive::empty();
829/// assert!(r.borrow().is_none());
830/// r.set("hello".to_string());
831/// assert_eq!(r.borrow().as_deref(), Some("hello"));
832/// ```
833#[cfg(feature = "std")]
834pub struct RcRecursive<T>(pub Rc<RefCell<Option<T>>>);
835
836#[cfg(feature = "std")]
837impl<T> Clone for RcRecursive<T> {
838    fn clone(&self) -> Self {
839        Self(self.0.clone())
840    }
841}
842
843#[cfg(feature = "std")]
844impl<T: fmt::Debug> fmt::Debug for RcRecursive<T> {
845    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
846        f.debug_tuple("RcRecursive").field(&self.0).finish()
847    }
848}
849
850#[cfg(feature = "std")]
851impl<T> Default for RcRecursive<T> {
852    fn default() -> Self {
853        Self::empty()
854    }
855}
856
857#[cfg(feature = "std")]
858impl<T> RcRecursive<T> {
859    /// Construct an empty (uninitialised) recursive anchor.
860    ///
861    /// # Examples
862    ///
863    /// ```
864    /// use noyalib::RcRecursive;
865    /// let r: RcRecursive<i32> = RcRecursive::empty();
866    /// assert!(r.borrow().is_none());
867    /// ```
868    #[must_use]
869    pub fn empty() -> Self {
870        Self(Rc::new(RefCell::new(None)))
871    }
872
873    /// Construct a recursive anchor pre-populated with `value`.
874    ///
875    /// # Examples
876    ///
877    /// ```
878    /// use noyalib::RcRecursive;
879    /// let r = RcRecursive::new(7_i64);
880    /// assert_eq!(r.borrow().as_ref().copied(), Some(7));
881    /// ```
882    #[must_use]
883    pub fn new(value: T) -> Self {
884        Self(Rc::new(RefCell::new(Some(value))))
885    }
886
887    /// Borrow the inner value immutably (runtime-checked).
888    #[must_use]
889    pub fn borrow(&self) -> core::cell::Ref<'_, Option<T>> {
890        self.0.borrow()
891    }
892
893    /// Borrow the inner value mutably (runtime-checked).
894    #[must_use]
895    pub fn borrow_mut(&self) -> core::cell::RefMut<'_, Option<T>> {
896        self.0.borrow_mut()
897    }
898
899    /// Replace the inner value, returning the previous one if any.
900    ///
901    /// # Examples
902    ///
903    /// ```
904    /// use noyalib::RcRecursive;
905    /// let r = RcRecursive::empty();
906    /// assert!(r.set(1_i32).is_none());
907    /// assert_eq!(r.set(2_i32), Some(1));
908    /// ```
909    pub fn set(&self, value: T) -> Option<T> {
910        self.borrow_mut().replace(value)
911    }
912
913    /// Drop the inner value, returning it if any.
914    #[must_use]
915    pub fn take(&self) -> Option<T> {
916        self.borrow_mut().take()
917    }
918
919    /// Number of strong `Rc` references to this recursive cell.
920    #[must_use]
921    pub fn strong_count(&self) -> usize {
922        Rc::strong_count(&self.0)
923    }
924
925    /// Downgrade to an [`RcRecursion`] weak reference. Useful to
926    /// break alias-only cycles when the anchored value is
927    /// referenced from multiple places — the weak reference does
928    /// not count towards the strong-count, so cycle storage is
929    /// released as soon as the last strong [`RcRecursive`] drops.
930    #[must_use]
931    pub fn downgrade(&self) -> RcRecursion<T> {
932        RcRecursion(Rc::downgrade(&self.0))
933    }
934}
935
936#[cfg(feature = "std")]
937impl<T: serde_core::Serialize> serde_core::Serialize for RcRecursive<T> {
938    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
939    where
940        S: serde_core::Serializer,
941    {
942        match &*self.borrow() {
943            Some(v) => v.serialize(serializer),
944            None => serializer.serialize_unit(),
945        }
946    }
947}
948
949#[cfg(feature = "std")]
950impl<'de, T: serde_core::Deserialize<'de>> serde_core::Deserialize<'de> for RcRecursive<T> {
951    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
952    where
953        D: serde_core::Deserializer<'de>,
954    {
955        T::deserialize(deserializer).map(Self::new)
956    }
957}
958
959/// Single-threaded weak recursive reference — pairs with
960/// [`RcRecursive`].
961///
962/// Use to encode alias-only edges in a cyclic graph that should
963/// not keep the anchored value alive on its own.
964#[cfg(feature = "std")]
965pub struct RcRecursion<T>(pub RcWeak<RefCell<Option<T>>>);
966
967#[cfg(feature = "std")]
968impl<T> Clone for RcRecursion<T> {
969    fn clone(&self) -> Self {
970        Self(self.0.clone())
971    }
972}
973
974#[cfg(feature = "std")]
975impl<T: fmt::Debug> fmt::Debug for RcRecursion<T> {
976    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
977        f.debug_tuple("RcRecursion").finish()
978    }
979}
980
981#[cfg(feature = "std")]
982impl<T> Default for RcRecursion<T> {
983    fn default() -> Self {
984        Self(RcWeak::new())
985    }
986}
987
988#[cfg(feature = "std")]
989impl<T> RcRecursion<T> {
990    /// Attempt to upgrade to a strong [`RcRecursive`]. Returns
991    /// `None` if every strong reference has been dropped.
992    pub fn upgrade(&self) -> Option<RcRecursive<T>> {
993        self.0.upgrade().map(RcRecursive)
994    }
995}
996
997/// Thread-safe recursive anchor type — the [`RcRecursive`]
998/// counterpart for cross-thread / parallel-parse use cases.
999///
1000/// Wraps `Arc<Mutex<Option<T>>>`. Access through
1001/// [`ArcRecursive::lock`] (rather than a `Deref`) so the locking
1002/// is explicit at the call site.
1003///
1004/// # Examples
1005///
1006/// ```
1007/// use noyalib::ArcRecursive;
1008/// let r: ArcRecursive<i32> = ArcRecursive::new(42);
1009/// assert_eq!(*r.lock(), Some(42));
1010/// ```
1011#[cfg(feature = "std")]
1012pub struct ArcRecursive<T>(pub Arc<Mutex<Option<T>>>);
1013
1014#[cfg(feature = "std")]
1015impl<T> Clone for ArcRecursive<T> {
1016    fn clone(&self) -> Self {
1017        Self(self.0.clone())
1018    }
1019}
1020
1021#[cfg(feature = "std")]
1022impl<T: fmt::Debug> fmt::Debug for ArcRecursive<T> {
1023    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1024        f.debug_tuple("ArcRecursive").finish()
1025    }
1026}
1027
1028#[cfg(feature = "std")]
1029impl<T> Default for ArcRecursive<T> {
1030    fn default() -> Self {
1031        Self::empty()
1032    }
1033}
1034
1035#[cfg(feature = "std")]
1036impl<T> ArcRecursive<T> {
1037    /// Construct an empty (uninitialised) thread-safe recursive
1038    /// anchor.
1039    #[must_use]
1040    pub fn empty() -> Self {
1041        Self(Arc::new(Mutex::new(None)))
1042    }
1043
1044    /// Construct a thread-safe recursive anchor pre-populated
1045    /// with `value`.
1046    #[must_use]
1047    pub fn new(value: T) -> Self {
1048        Self(Arc::new(Mutex::new(Some(value))))
1049    }
1050
1051    /// Lock the inner cell. Recovers from poisoning rather than
1052    /// panicking — the only way the mutex gets poisoned is a
1053    /// panic mid-write inside the critical section, and the
1054    /// recovered guard is still observable as `None` or as the
1055    /// pre-panic value.
1056    ///
1057    /// Returns a `MutexGuard` over `Option<T>`.
1058    ///
1059    /// # Examples
1060    ///
1061    /// ```
1062    /// use noyalib::ArcRecursive;
1063    /// let r = ArcRecursive::new("hi".to_string());
1064    /// let guard = r.lock();
1065    /// assert_eq!(guard.as_deref(), Some("hi"));
1066    /// ```
1067    pub fn lock(&self) -> std::sync::MutexGuard<'_, Option<T>> {
1068        self.0.lock().unwrap_or_else(|e| e.into_inner())
1069    }
1070
1071    /// Replace the inner value, returning the previous one if any.
1072    pub fn set(&self, value: T) -> Option<T> {
1073        self.lock().replace(value)
1074    }
1075
1076    /// Drop the inner value, returning it if any.
1077    #[must_use]
1078    pub fn take(&self) -> Option<T> {
1079        self.lock().take()
1080    }
1081
1082    /// Number of strong `Arc` references to this recursive cell.
1083    #[must_use]
1084    pub fn strong_count(&self) -> usize {
1085        Arc::strong_count(&self.0)
1086    }
1087
1088    /// Downgrade to an [`ArcRecursion`] weak reference.
1089    #[must_use]
1090    pub fn downgrade(&self) -> ArcRecursion<T> {
1091        ArcRecursion(Arc::downgrade(&self.0))
1092    }
1093}
1094
1095#[cfg(feature = "std")]
1096impl<T: serde_core::Serialize> serde_core::Serialize for ArcRecursive<T> {
1097    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1098    where
1099        S: serde_core::Serializer,
1100    {
1101        match &*self.lock() {
1102            Some(v) => v.serialize(serializer),
1103            None => serializer.serialize_unit(),
1104        }
1105    }
1106}
1107
1108#[cfg(feature = "std")]
1109impl<'de, T: serde_core::Deserialize<'de>> serde_core::Deserialize<'de> for ArcRecursive<T> {
1110    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1111    where
1112        D: serde_core::Deserializer<'de>,
1113    {
1114        T::deserialize(deserializer).map(Self::new)
1115    }
1116}
1117
1118/// Thread-safe weak recursive reference — pairs with
1119/// [`ArcRecursive`].
1120#[cfg(feature = "std")]
1121pub struct ArcRecursion<T>(pub ArcWeak<Mutex<Option<T>>>);
1122
1123#[cfg(feature = "std")]
1124impl<T> Clone for ArcRecursion<T> {
1125    fn clone(&self) -> Self {
1126        Self(self.0.clone())
1127    }
1128}
1129
1130#[cfg(feature = "std")]
1131impl<T: fmt::Debug> fmt::Debug for ArcRecursion<T> {
1132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1133        f.debug_tuple("ArcRecursion").finish()
1134    }
1135}
1136
1137#[cfg(feature = "std")]
1138impl<T> Default for ArcRecursion<T> {
1139    fn default() -> Self {
1140        Self(ArcWeak::new())
1141    }
1142}
1143
1144#[cfg(feature = "std")]
1145impl<T> ArcRecursion<T> {
1146    /// Attempt to upgrade to a strong [`ArcRecursive`]. Returns
1147    /// `None` if every strong reference has been dropped.
1148    pub fn upgrade(&self) -> Option<ArcRecursive<T>> {
1149        self.0.upgrade().map(ArcRecursive)
1150    }
1151}