2-step initialization for Rc/Arc

I'm writing this following a discussion and suggestion given in the Upgrading "dead" Weak< UnsafeCell< MaybeUninit<T>>> to Rc<T> - #13 by matrixdev - help - The Rust Programming Language Forum thread.

What is the proposal?

In short the proposal is to split Rc/Arc::new_cyclic function into two parts. First part is the creation of "empty" Rc wrapper which can provide Weak references that cannot be upgraded at this stage. Second part is to finalize Rc/Arc creation by initializing its content and allowing Weak references to be upgraded.

Why do we need it?

Rc/Arc::new_cyclic has a lot of limitation when it comes to error handling and async. Instead of writing try_new_cyclic, maybe_new_cyclic, new_cyclic_async, try_new_cyclic_async, maybe_new_cyclic_async (or how many more will come up in the future) it should be easier to just separate RcBox allocation and first strong reference creation. This is basically Rc/Arc::new_cyclic but split in half.

Example

I'll try to give example from library I've unsuccessfully tried to implement (mainly because of the public API limitations) but the idea should be the same:

let maybe: MaybeRc<Self> = MaybeRc::new();

let weak: Weak<Self> = maybe.downgrade();
assert!(weak.upgrade().is_none());

// Child creation is async and can fail here
// Rc::new_cyclic will not work here
let child: Rc<Child> = Child::new(weak.clone()).await?;

let rc: Rc<Self> = maybe.into_rc(Self {
    child,
});
assert!(weak.upgrade().is_some());

Additionally it can be easily convertd into UniqueRc to get mutable reference for further initialization when it goes live:

let rc: UnigueRc<Self> = maybe.into_unique_rc(Self {
    child,
});
assert!(weak.upgrade().is_none());

Considered alternatives?

  1. writing own library - doesn't work because we can't increment strong counter wich is 0
  2. relaxing Rc::increment_strong_count/Rc::decrement_strong_count requirements so it is allowed to work with strong counter = 0. this will allow above libraries to be implemented. Not sure about Arc at this time because of Acquire/Release on atomics.
  3. add special unsafe method to increase strong count specifically from 0 to 1
  4. mentioned above UniqueRc. but it doesn't cover this case, you can read my comment there.
  5. UniqueRc from RustForLinux project. It does provide ability for delayed initialization but doesn't allow Weak references creation. It also doesn't use std Rc/Arc so mostly out of question

Edited: typos, more alternatives

1 Like

as mentioned here: Upgrading "dead" Weak< UnsafeCell< MaybeUninit<T>>> to Rc<T> - #16 by programmerjake - help - The Rust Programming Language Forum

all you need to effectively convert the Relaxed atomic to a Release atomic is to put a Release thread fence after all writes and before the Relaxed atomic.

There is a problem with the barrier is that convert the Relaxed atomic to a Release is an internal implementation detail that cannot be relied upon.

Neverthless it will still require multiple changes in the std:

  1. changing Relaxed and documenting this behavior
  2. allowing strong count 0 -> 1
  3. also internally increase weak count by 1 when strong count goes 0 -> 1 (as this detail is also internal implementation detail that can't be relied upon)

What I suggested in the other thread is that there’s also the alternative not to modify the increment_strong_count methods, but to add a new unsafe method specifically to turn a counter from zero to one.

1 Like

That is also a good one, I somehow misread it.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.