Skip to content

Commit f80aca3

Browse files
committed
Add Cyclic to help build cyclic structures
This is necessary since rust-lang/rust#112566 has not yet been merged into stable. This method uses nested Rc::new_cyclic calls to achieve a similar effect to `RcUninit`.
1 parent 14c02a7 commit f80aca3

File tree

3 files changed

+127
-2
lines changed

3 files changed

+127
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "bypass"
3-
version = "0.3.1"
3+
version = "0.3.2"
44
edition = "2024"
55
authors = ["Kevin Robert Stravers <[email protected]>"]
66
description = "Thread-local dynamic variables"

src/lib.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ use std::{
177177
cell::RefCell,
178178
collections::{BTreeMap, HashMap},
179179
fmt, panic,
180+
rc::Rc,
180181
};
181182

182183
/// Creates a scope.
@@ -796,3 +797,127 @@ impl ScopeInner {
796797
}
797798
}
798799
}
800+
801+
/// A builder for constructing cyclic data structures within a [Scope].
802+
///
803+
/// The [Cyclic] struct provides an interface for creating sets of
804+
/// objects with cyclic (self-referential or mutually referential)
805+
/// relationships. It automates the creation and storage of [Rc] and
806+
/// [Weak](std::rc::Weak) pointers in the given scope, enabling the
807+
/// construction of graphs, trees with back-references, or other cyclic
808+
/// structures.
809+
///
810+
/// # How It Works #
811+
///
812+
/// - Each call to [then](Cyclic::then) registers a constructor closure
813+
/// associated with a unique name.
814+
/// - When [construct](Cyclic::construct) is called:
815+
/// - All registered names are first populated in the scope as `{name}/weak`
816+
/// keys, each holding a `Weak<R>` reference to the eventual value.
817+
/// - Then, each closure is executed in order, allowing each to access the
818+
/// previously registered strong and weak references via the scope.
819+
/// - The resulting value of each closure is stored under its name as a strong
820+
/// [Rc] reference.
821+
///
822+
/// This allows closures to refer to both earlier and later entries. Later
823+
/// entries can only be weakly referenced. Earlier entries can be strongly
824+
/// referenced.
825+
///
826+
/// # Examples #
827+
///
828+
/// ```
829+
/// use bypass::{scope, Cyclic};
830+
/// use std::rc::{Rc, Weak};
831+
///
832+
/// scope!(static MAIN);
833+
///
834+
/// MAIN.scope(|| {
835+
/// Cyclic::new(&MAIN, |_| {})
836+
/// .then("first", || {
837+
/// // Access a weak reference to a subsequent `then` entry.
838+
/// let weak_second: Weak<String> = MAIN.get("second/weak");
839+
/// println!("first: Strong count of 'second': {}", Weak::strong_count(&weak_second));
840+
///
841+
/// 123i32
842+
/// })
843+
/// .then("second", || {
844+
/// // Access a strong reference to a previous `then` entry.
845+
/// let strong_first: Rc<i32> = MAIN.get("first");
846+
/// println!("second: Value of 'first': {}", *strong_first);
847+
/// String::from("lorem ipsum")
848+
/// })
849+
/// .construct();
850+
/// });
851+
/// ```
852+
pub struct Cyclic<F: FnOnce(&'static Scope)> {
853+
scope: &'static Scope,
854+
function: F,
855+
}
856+
857+
impl<X: FnOnce(&'static Scope)> Cyclic<X> {
858+
/// Creates a new cyclic builder with an initializer.
859+
///
860+
/// Function `initializer` will be run before all [then](Cyclic::then)
861+
/// clauses, but after all weak pointers have been added to the scope.
862+
///
863+
/// # Examples #
864+
///
865+
/// ```
866+
/// use bypass::{scope, Cyclic};
867+
/// use std::rc::{Rc, Weak};
868+
///
869+
/// scope!(static MAIN);
870+
///
871+
/// MAIN.scope(|| {
872+
/// Cyclic::new(&MAIN, |scope| {
873+
/// scope.insert("x", 123);
874+
/// // We can get a weak pointer to subsequent `then`s.
875+
/// let y: Weak<usize> = scope.get("y/weak");
876+
/// })
877+
/// .then("y", || {
878+
/// let value: i32 = MAIN.get("x");
879+
/// assert_eq!(value, 123);
880+
///
881+
/// 9usize
882+
/// })
883+
/// .construct();
884+
/// });
885+
/// ```
886+
pub fn new(scope: &'static Scope, initializer: X) -> Self {
887+
Self {
888+
scope,
889+
function: initializer,
890+
}
891+
}
892+
}
893+
894+
impl<F: FnOnce(&'static Scope)> Cyclic<F> {
895+
/// Adds a cyclic dependency.
896+
pub fn then<N: Into<CowStr>, G: FnOnce() -> R, R: 'static>(
897+
self,
898+
name: N,
899+
g: G,
900+
) -> Cyclic<impl FnOnce(&'static Scope)> {
901+
let scope = self.scope;
902+
let function = self.function;
903+
let name = name.into();
904+
905+
Cyclic {
906+
scope,
907+
function: move |scope| {
908+
let name_clone = name.clone();
909+
let strong = Rc::new_cyclic(|weak| {
910+
scope.insert(format!("{}/weak", name_clone), weak.clone());
911+
function(scope);
912+
g()
913+
});
914+
scope.insert(name, strong);
915+
},
916+
}
917+
}
918+
919+
/// Constructs the set of cyclic dependencies.
920+
pub fn construct(self) {
921+
(self.function)(self.scope);
922+
}
923+
}

0 commit comments

Comments
 (0)