-
Notifications
You must be signed in to change notification settings - Fork 12.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking Issue for UniqueRc
/UniqueArc
#112566
Comments
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
I wonder whether it would be desirable to also add a fallible method which converts the other way, from an |
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 |
Name idea: As I understand it, this struct is the "root" owner of a ref-counted thing, off of which you can "branch off" |
At some point you may want to add support for the allocator API (after #89132 lands in a few hours) |
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 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 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 I like the idea of I'd much rather suggest adding something like 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,
}))
} |
FYI |
I agree that |
I have a Yew web application that shares state via an |
(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? |
I would like to ask: what is the advantage of As mentioned,
Proof that struct UniqueRc<T> {
value: T,
empty: UninitRc<T>
}
impl<T> UniqueRc<T> {
pub fn new(value: T) -> UniqueRc<T> {
Self { value, empty: UninitRc::new() }
}
pub fn downgrade(this: &UniqueRc<T>) -> Weak<T> {
UninitRc::downgrade(&this.empty)
}
pub fn into_rc(this: UniqueRc<T>) -> Rc<T> {
UninitRc::init(this.empty, this.value)
}
} Proof that impl<T> Rc<T> {
pub fn new_cyclic<F>(data_fn: F) -> Rc<T>
where
F: FnOnce(&Weak<T>) -> T {
let uninit = UninitRc::new();
let value = (data_fn)(&UninitRc::downgrade(&uninit));
UninitRc::init(uninit, value)
}
} |
For We can add a similar method impl<T> UniqueRc<MaybeUninit<T>> {
pub fn write(rc: UniqueRc<MaybeUninit<T>>, value: T) -> UniqueRc<T> { /* ... */ }
} |
While this is a good start it does not remove the need for Here is what I think is a sound implementation of pub struct UninitRc<T> {
inner: UniqueRc<MaybeUninit<T>>,
}
impl<T> UninitRc<T> {
pub fn new() -> Self {
UninitRc {
inner: UniqueRc::new(MaybeUninit::uninit()),
}
}
pub fn downgrade(&self) -> Weak<T> {
let weak = UniqueRc::downgrade(&self.inner);
// SAFETY: the weak reference can only be upgraded after initialization
unsafe { transmute(weak) }
}
pub fn init(mut self, val: T) -> Rc<T> {
self.inner.write(val);
let rc = UniqueRc::into_rc(self.inner);
// SAFETY: the rc has been initialized
unsafe { transmute(rc) }
}
} |
I have used the above implementation of UninitRc in my private projects, and it’s very useful. However, as someone on Reddit pointed out to me, transmuting Rc and Weak is unsound. There’s another method for Rc<MaybeUninit> that does the same thing soundly in the feature new_uninit. There’s no equivalent for Weak, so that has to be implemented. https://doc.rust-lang.org/std/rc/struct.Rc.html#method.assume_init #![feature(new_uninit)]
pub fn init(mut self, val: T) -> Rc<T> {
self.inner.write(val);
let rc = UniqueRc::into_rc(self.inner);
// SAFETY: the rc has been initialized
unsafe { rc.assume_init() }
} |
@SveOls could you please explain why transmuting Rc and Weak is unsound in this case or link me to some resources? |
(This is somewhat off-topic, but) @LHolten Not sure if this is what SveOls meant, but generally unless explicit guarantees about layout/representation are listed in the documentation, it is not considered sound to transmute between different type-generic instantiations of a type. One way to soundly "transmute" Rc::from_raw doc excerpt
(The |
@LHolten in other words -- by default, all transmutes are unsound; it is up to you as user of transmute to find documentation which says it is sound. |
Yep, you're right, @zachs18, that's what I meant. I'll make sure to be more explicit in the future. On another note, my interpretation of the drop implementation of Weak says that it never drops T. When the strong count reaches 0, T is dropped, but the memory remains, and when the weak count reaches 0, the memory is freed. Meaning that casting Weak<MaybeUninit> to Weak should be okay, as it won't call drop on uninitialized memory. That, combined with a more sound way to cast Rc<MaybeUninit> and Weak<MaybeUninit> to Rc and Weak, should cover the issues with soundness. |
I noticed that this has an implicit |
Nope, looks like it was recently relaxed in #126285 |
Newbie to Rust. How does this compare to a IIUC, the point is that the wrapper metadata is larger with I.e. the only use for this is as a precursor for an Am I understanding the lay of the land correctly? The docs currently state
and there is no mention of |
If you want a Ultimately the usecase for this is "I want something that can be converted to a |
The problem to me is that the docs assume you already know this, and it doesn't rule out I think it would be nice to guide readers who need simple ownership to And replace the non-excluding use-case explanation in the docs:
with the very concrete, specific use-case, statement you just gave here:
|
|
`UniqueRc` trait impls UniqueRc tracking Issue: rust-lang#112566 Stable traits: (i.e. impls behind only the `unique_rc_arc` feature gate) * Support the same formatting as `Rc`: * `fmt::Debug` and `fmt::Display` delegate to the pointee. * `fmt::Pointer` prints the address of the pointee. * Add explicit `!Send` and `!Sync` impls, to mirror `Rc`. * Borrowing traits: `Borrow`, `BorrowMut`, `AsRef`, `AsMut` * `Rc` does not implement `BorrowMut` and `AsMut`, but `UniqueRc` can. * Unconditional `Unpin`, like other heap-allocated types. * Comparison traits `(Partial)Ord` and `(Partial)Eq` delegate to the pointees. * `PartialEq for UniqueRc` does not do `Rc`'s specialization shortcut for pointer equality when `T: Eq`, since by definition two `UniqueRc`s cannot share an allocation. * `Hash` delegates to the pointee. * `AsRawFd`, `AsFd`, `AsHandle`, `AsSocket` delegate to the pointee like `Rc`. * Sidenote: The bounds on `T` for the existing `Pointer<T>` impls for specifically `AsRawFd` and `AsSocket` do not allow `T: ?Sized`. For the added `UniqueRc` impls I allowed `T: ?Sized` for all four traits, but I did not change the existing (stable) impls. Unstable traits: * `DispatchFromDyn`, allows using `UniqueRc<Self>` as a method receiver under `feature(arbitrary_self_types)`. * Existing `PinCoerceUnsized for UniqueRc` is generalized to allow non-`Global` allocators, like `Rc`. * `DerefPure`, allows using `UniqueRc` in deref-patterns under `feature(deref_patterns)`, like `Rc`. For documentation, `Rc` only has documentation on the comparison traits' methods, so I copied/adapted the documentation for those, and left the rest without impl-specific docs. ~~Edit: Marked as draft while I figure out `UnwindSafe`.~~ Edit: Ignoring `UnwindSafe` for this PR
Feature gate:
#![feature(unique_rc_arc)]
This is a tracking issue for
UniqueRc
andUniqueArc
, 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 implementDerefMut
). Weak pointers to the object can be made, but upgrading these will fail until it has been converted into a regular RC.Public API
Steps / History
UniqueRc
: Addalloc::rc::UniqueRc
#111849UniqueArc
Unresolved Questions
EmptyRc
/EmptyArc
?is_unique
methods? Could we add this functionality toRc
/Arc
/ instead? (comment)UniqueRc
? (comment)Footnotes
https://std-dev-guide.rust-lang.org/feature-lifecycle/stabilization.html ↩
The text was updated successfully, but these errors were encountered: