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}