Copy/paste Rc::new_cyclic into stable

  1. I am referring to the code at rc.rs - source

  2. I am on Rust stable, and prefer to stay on stable. I would like to copy/paste this one function into my own RcUtil struct.

  3. The problem I am running into is that this appears to use some private Rc functions, like Rc::from_inner, as well as the private 'ptr' field of Weak

Question: Is there anyway to copy/paste & patch this code to make it work in Rust stable?

You could transfer it to a custom Rc implementation, but not implement it for std::rc::Rc in any way I think, since it touches internals.

Any custom Rc implementation can be reasonably complete but there are some features that are not available even there, in the same way as Own Arc implementation outside of standard library? - #2 by RustyYato

3 Likes

@bluss: Thanks, this (1) is really insightful and (2) sounds like a mess to attempt. I have re-engineered my code to not depend on new_cyclic. :slight_smile:

To create a cyclic structure, you can first construct it with a dummy value, get a Weak to your Rc, then use get_mut_unchecked to update the value to something using the Weak.

@alice: I don't understand your proposed solution. Are you arguing for or against "new_cyclic can be built on rust stable" ?

I think it's possible, but only with restrictions.

pub fn rc_new_cyclic<T: Default>(data_fn: impl FnOnce(&Weak<T>) -> T) -> Rc<T> {
    let rc = Rc::new(T::default());
    let data = data_fn(&Rc::downgrade(&rc));
    assert_eq!(Rc::strong_count(&rc), 1);

    // Workaround for not having `Rc::get_mut_unchecked` on stable.
    let raw = Rc::into_raw(rc);
    // SAFETY: We don't upgrade any of the `Weak`s that exist
    // while we write the data to its slot.
    *unsafe { &mut *(raw as *mut T) } = data;
    unsafe { Rc::from_raw(raw) }
}

Miri doesn't complain about this code, so it does seem to be sound. There is one slight issue that if the destructor of a T::default() value panics this code will leak memory, but that can be easily fixed with a drop guard that recovers the Rc.

1 Like

Oh, I didn't realize that get_mut_unchecked was unstable. Anyway, by using @Kestrer's workaround for that, you can still do what I suggested.

You can do something that is equivalent to new_cyclic, but I do not think that you could provide a safe API to it because the Weak is not "inaccessible" while constructing the value in the same way as it is with the real new_cyclic.

1 Like

I wouldn't be so sure: Playground, to be run with Miri.

Luckily, the fix is easy: swap the two T instances first, and only drop the initial one after having done that :slightly_smiling_face:

unsafe {
    let prev: T = ::core::ptr::replace(raw as *mut T, data);
    /* Bonus: we could `Rc::from_raw()` here to avoid the leak-on-panic */
    drop(prev);
}

I wonder if the following would be sound (since it is less clear-cut, I wouldn't risk it):

unsafe {
    let raw_mut: *mut T = raw as *mut T;
    ::core::ptr::drop_in_place(raw_mut);
    // in between these two operations `raw_mut` may be aliased ≠ `*raw_mut = data;`
    raw_mut.write(data);
}
1 Like

Huh, I thought I had a way to break it even if no aliasing was assumed when you replaced the value in it, but the

assert_eq!(Rc::strong_count(&rc), 1);

prevented it.