Summary
This is an RFC to add APIs to std::rc::Weak to allow recover shared object after every Rcs are dropped.
Identical APIs will be added for std::sync::Weak.
Also, it proposes additional guarantee to align of Rc's and Arc's underlying pointer that it should be greater than or equal to align of usize.
Motivation
Currently rc::Weak doesn’t own shared object, but still own memory allocation itself. It means shared object will be drop()ed when every Rcs are dropped, but underlying memory will not be freed until every Weaks also are dropped.
But after shared object is dropped, Weak becomes completely useless even it still owns shared allocation. It’s conceptually like shared Box<Option<T>>, but cannot turn it “on” after turned off.
Guide-level explanation
This RFC proposes some methods to allow re-construct shared object from std::rc::Weak handle using existing allocated memory after shared object is dropped. It also covers std::sync::Weak, with non-blocking atomic operations.
Also, with this RFC Weak::new() becomes useful as it can be used for lazy-construction of shared object. So this RFC also propose small change to semantics of Weak::new() that it does not allocate memory until recovered or cloned. To do this without change Weak's size and make it zero-cost, additional guarantee will be added that align of RcBox is equal or greater than of usize.
Reference-level explanation
Pointer alignment
Result of Rc::into_raw(Self) -> *const T and Arc::into_raw(Self) -> *const T pointer must be aligned with at least mem::align_of::<usize>().
Additional methods of std::rc::Weak<T>
/// Returns `true` if underlying object is alive
fn can_upgrade(&self) -> bool;
/// Try upgrade, and recover if failed
fn upgrade_or_recover<F: FnOnce() -> T>(&self, f: F) -> Rc<T>;
Additional methods of std::sync::Weak<T>
/// Returns `true` if underlying object is alive
/// Note that this state can always be changed by other threads
fn can_upgrade(&self) -> bool;
/// Try upgrade, and try recover if failed
/// Return `Err` if another thread is already doing it.
fn try_upgrade_or_recover<F: FnOnce() -> T>(&self, f: F) -> Result<Arc<T>, RecoverError>;
Changed semantics
Both std::rc::Weak::new() and std::sync::Weak::new() will not allocate memory until cloned or recovered.
Drawbacks
This adds complexities to stdlib.
Alternatives
-
Simulate this as a crate using Rc<RefCell<Option<T>>>.
-
Skip changing semantics of Weak::new().
-
Skip guaranteed pointer alignment.
-
Only implement this for Rc and skip for Arc
Unresolved questions
Should we need another recovery method that does not tries to upgrade first?