I'm wondering about natively supporting initialization of Weak at a later point, instead of inside Rc::new_cyclic's data_fn. I propose this to look something like the following.
use std::rc::{RcInit, Rc};
let init_a = RcInit::new();
// Get the weak pointer, points to allocated memory for type some type T
// Weak::upgrade will return None at this point.
let weak = init_a.weak().clone();
// Consumes init_a, preventing double initialization. Since no Rc exists to this instance, we are
// guaranteed that nothing is borrowing anything. UB is avoided.
let a: Rc<T> = init_a.init(T::new());
// Upgrading the weak now succeeds.
let _ = weak.upgrade().unwrap();
This makes it easier to create a bunch objects that need weak references, as we don't need to nest each construction inside of Rc::new_cyclic. It prevents potential stack overflows if large amounts of objects need to be created like a linked list with weak references to parents.
This also allows construction inside async functions, since Rc::new_cyclic is synchronous, we can't call await from within it (example use-case in the wild: here)
A similar pattern can be used for Arc.
Overall, this pattern is more flexible, and I believe it can be implemented behind a fully safe API.
I don't see why this wouldn't work! Essentially using type-state to be able to give Rc s an additional "possible state" without having to store any more runtime data for them. And it's less magical-looking than new_cyclic, which is good.
Note that this CAN all be implemented in an external crate if you make your own copy of the Rc type, and several crates (like triomphe) implement versions of Rc/Arc with additional features, though I don't know of a crate implementing this one in particular.
I'm a bit confused as to why the tracking issue mentions RcUninit (Cyclic Arc and Rc Builder · Issue #90 · rust-lang/libs-team · GitHub), but instead implements UniqueRc which doesn't solve the problem in an optimal manner since it requires a value to be provided upon construction.
While UniqueRc does allow for cyclic data structures to be created, it must be done by mutating the UniqueRc. Mutation is prone to creating reference cycles. Construction-only assignment of fields, without any mutation to "set" the struct afterwards cannot generate reference cycles. It's also more cumbersome to work with.
For instance, if we have objects A, B, and C, and we want these to connect as A => B => C -> A (=> being strong, -> being weak), then we must do something along the following lines:
let mut a_uniq = UniqueRc::new(A::new());
let a_weak = UniqueRc::downgrade(&a_uniq);
let c = Rc::new(C::new(a_weak));
let b = Rc::new(B::new(c));
a_uniq.set_b(b);
let a = a_uniq.into_rc();
To implement A::set_b, the field A::b must either be
Option<Rc<B>>: Requires unwrap/clone for each access.
MaybeUninit<Rc<B>>: Requiring unsafe.
Weak<B>: Requiring upgrade for every access.
The above also makes it easier to make mistakes in more complex programs where we don't have the full picture.
It is not hard to change the above into Rc<RefCell<A>>, and then provide this pointer to C, which would cause a reference cycle to be created once a.borrow_mut().set_b(b) gets called.
On the other hand RcUninit doesn't have this problem, since initialization is deferred. The equivalent would look like the following.
let a_uninit = RcUninit::new();
let b_uninit = RcUninit::new();
let c_uninit = RcUninit::new();
let c = c_uninit.init(C::new(a_uninit.weak()));
let b = b_uninit.init(B::new(c));
let a = a_uninit.init(b);
One interesting point of RcUnique is that we can have RcUnique<MaybeUninit<T>>::write that returns the initialized RcUnique<T> similar to Box::write, which you called init. It's therefore not clear if we need the uninitialized state as a separate type or if that is not a clean representation already.
Quite sorry for the situation here and just repeating wasn't the intention but that tracking issue isn't tracking all it should. That comment is somewhere in the unexpanded items by default and the open questions list no indication of UninitRc at all. Would you mind updating it to appropriately?