Skip to content

Tracking Issue for UniqueRc/UniqueArc #112566

Open

Description

@eholk

View all comments

Feature gate: #![feature(unique_rc_arc)]

This is a tracking issue for UniqueRc and UniqueArc, as discussed in rust-lang/libs-team#90.

This feature supports creating cyclic data structures by creating an RC that is guaranteed to be uniquely owned. The uniquely owned RC can then be converted to a regular RC. While uniquely owned, we can freely modify the contents of the UniqueRc/UniqueArc (i.e. they implement DerefMut). Weak pointers to the object can be made, but upgrading these will fail until it has been converted into a regular RC.

Public API

let rc = UniqueRc::new(42);
let weak = UniqueRc::downgrade(&rc);
assert!(weak.upgrade().is_none());

let _rc = UniqueRc::into_rc(rc);
assert_eq!(*weak.upgrade().unwrap(), 42);

Steps / History

Unresolved Questions

  • Do we also want EmptyRc/EmptyArc?
  • Are we concerned about confusion around the existing is_unique methods? Could we add this functionality to Rc/Arc/ instead? (comment)
  • Do we want consistency with Linux Kernel UniqueRc? (comment)

Footnotes

  1. https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩

Activity

  1. added
    T-libs-api[DEPRECATED; DO NOT USE]
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Jun 12, 2023
  2. changed the title [-]Tracking Issue for XXX[/-] [+]Tracking Issue for `UniqueRc`/`UniqueArc`[/+] on Jun 12, 2023
  3. self-assigned this
    on Jun 12, 2023
  4. programmerjake commented on Jun 12, 2023

    @programmerjake
  5. eholk commented on Jun 12, 2023

    @eholk
    Author
  6. ebkalderon commented on Jun 29, 2023

    @ebkalderon
    Contributor

    I wonder whether it would be desirable to also add a fallible method which converts the other way, from an Rc<T> back into UniqueRc<T> provided there is only one strong reference, as part of this ticket? Has this been considered, and if so, are there any drawbacks?

  7. eholk commented on Jun 29, 2023

    @eholk
    ContributorAuthor

    I wonder whether it would be desirable to also add a fallible method which converts the other way, from an Rc<T> back into UniqueRc<T> provided there is only one strong reference, as part of this ticket? Has this been considered, and if so, are there any drawbacks?

    Good question. I think it'd help to see a concrete use case for that feature. I'm curious how often in practice you can have enough confidence that an Rc is uniquely owned so that it would make sense to convert it into a UniqueRc. Maybe for some kind of manual deinitialization process? Or maybe there are programs that have a structure that is shared for a phase of the program (e.g. spawn a bunch of threads to process it in parallel) and then goes back to being a single owner (e.g. the threads all shut down after their work is finished).

  8. camsteffen commented on Jun 30, 2023

    @camsteffen
    Contributor

    Name idea: RootRc

    As I understand it, this struct is the "root" owner of a ref-counted thing, off of which you can "branch off" Weak Rc's.

  9. tgross35 commented on Jun 30, 2023

    @tgross35
    Member

    At some point you may want to add support for the allocator API (after #89132 lands in a few hours)

  10. MatrixDev commented on Jul 19, 2023

    @MatrixDev

    I got here from rust-lang/libs-team#90 so maybe I'm missing something.

    I see one major downside here (at least from my usecases). Most of the time when I need cyclic dependencies - it's when they should be em... cyclic.

    I assume that idea for using UniqueRc for circular dependencies is following:

    mod child {
        pub struct Child {
            parent: Weak<super::Parent>,
        }
    
        impl Child {
            pub fn new(parent: Weak<super::Parent>) -> Option<UniqueRc<Self>> {
                Some(Self {
                    parent,
                })
            }
        }
    }
    
    struct Parent {
        child: Rc<child::Child>,
    }
    
    impl Parent {
        fn new() -> Option<Rc<Self>> {
            let child = child::Child::new(Weak::new())?;
            Rc::new_cyclic(|weak| {
                child.parent = weak.clone(); // ERROR: parent cannot be updated, field is private
                Self {
                    child: UniqueRc::into_rc(child),
                }
            })
        }
    }

    As you can see it doesn't help us in such case. And exposing parent field is not always desirable.
    Another problem here is that it is almost impossible to update parent field if it is propagted to the children of the child and so on. Also by this time they are most probably were already converted from UniqueRc to Rc.

    Maybe idea is to use it other way around:

    fn new() -> Option<Rc<Self>> {
        let rc = UnqueRc::new(Self { /* no way to initialize without client instance */ });
    
        rc.child = child::Child::new(UniqueRc::downgrade(&rc))?;
    
        Some(UniqueRc::into_rc(rc))
    }

    But as you can see we can't even create Parent this way... We could wrap child in Option, but it will have huge impact on the usability of this field. So basically I don't see how it should help with circular dependencies in non-trivial cases.

    I like the idea of UniqueRc and it has its place but IMHO not as a solution for circular dependencies.

    I'd much rather suggest adding something like MaybeRc<T> (naming similar to MaybeUninit) that can be later materialized into Rc<T>. This would solve all problems with current cyclic dependencies that at very least I see in my usecases.

    fn new() -> Result<Rc<Self>, Error> {
        let rc = MaybeRc::<Self>::new();
        let weak = rc.downgrade();
    
        let child = Child::new(weak)?;
    
        Ok(rc.materialize(Self {
            child,
        }))
    }
  11. SkiFire13 commented on Jul 20, 2023

    @SkiFire13
    Contributor

    I'd much rather suggest adding something like MaybeRc<T> (naming similar to MaybeUninit)

    FYI MaybeUninit is named like this because it could be uninitialized, but not necessarily. Your proposed MaybeRc<T> instead is always uninitialized because the moment you initialize it you have to consume it, getting back a Rc. So it has the Uninit part of MaybeUninit, but not the Maybe. UninitRc/EmptyRc seem a more fitting name to me.

  12. MatrixDev commented on Jul 21, 2023

    @MatrixDev

    UninitRc/EmptyRc seem a more fitting name to me.

    I agree that MaybeRc is not the best name but don't like EmptyRc either. Maybe LateRc or LazyRc?

  13. finnbear commented on Jul 29, 2023

    @finnbear
    Contributor

    I wonder whether it would be desirable to also add a fallible method which converts the other way, from an Rc<T> back into UniqueRc<T> provided there is only one strong reference, as part of this ticket? Has this been considered, and if so, are there any drawbacks?

    Good question. I think it'd help to see a concrete use case for that feature. I'm curious how often in practice you can have enough confidence that an Rc is uniquely owned so that it would make sense to convert it into a UniqueRc. Maybe for some kind of manual deinitialization process? Or maybe there are programs that have a structure that is shared for a phase of the program (e.g. spawn a bunch of threads to process it in parallel) and then goes back to being a single owner (e.g. the threads all shut down after their work is finished).

    I have a Yew web application that shares state via an Rc to avoid cloning large structs. I currently use a hacky workaround which was later pointed out to be unsound. Converting an Rc to a UniqueRc is exactly what I need, as the structure of my program guarantees that the strong ref count will be 1 when I need to do mutations.

  14. RalfJung commented on Sep 5, 2023

    @RalfJung
    Member

    (Mirroring my comment from the closed ACP)

    Comparing what is implemented here with the original proposal, there seems to be one downside: with UniqueRc, one has to first set up the Gadget in an invalid state, and then close the loop. Doesn't that share the downsides of what was described in the "Multi-phase Construction" proposal in the ACP?

  15. 21 remaining items

  16. jpetkau commented on Jun 30, 2025

    @jpetkau

    I don't know if this is helpful, but I have a crate genrc which adds the same feature in a replacement Rc type (along with the ability to "project" an Rc to a contained subobject, like C++'s shared_ptr.) It uses the names RcBox / ArcBox for the unique variants, which I copied from the crate rc-box.

  17. LeoPloutno commented on Nov 21, 2025

    @LeoPloutno

    Maybe I don't understand something about this API, but why can't an Rc be (optionally) converted to a UniqueRc when the strong count is 1? Same thing concerning Arc -> UniqueArc

  18. zachs18 commented on Nov 21, 2025

    @zachs18
    Contributor

    Maybe I don't understand something about this API, but why can't an Rc be (optionally) converted to a UniqueRc when the strong count is 1? Same thing concerning Arc -> UniqueArc

    If I understand what you're asking, that's what this other ACP is for: rust-lang/libs-team#608 . It has been approved (rust-lang/libs-team#608 (comment) ), but not yet implemented.

  19. added
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    and removed
    T-libs-api[DEPRECATED; DO NOT USE]
    on Aug 12, 2026
  20. added a commit that references this issue on Sep 18, 2026
  21. added a commit that references this issue on Sep 19, 2026
  22. added 2 commits that reference this issue on Sep 19, 2026
  23. added a commit that references this issue on Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCT-libsRelevant to the library team, which will review and decide on the PR/issue.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions