-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Add Arc::unwrap_or_drop
for safely discarding Arc
s without calling the destructor on the inner type.
#75911
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
Add Arc::unwrap_or_drop
for safely discarding Arc
s without calling the destructor on the inner type.
#75911
Changes from all commits
a534492
8af2a40
1ceee61
838e5ed
08455a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -469,6 +469,17 @@ impl<T> Arc<T> { | |||||
/// | ||||||
/// This will succeed even if there are outstanding weak references. | ||||||
/// | ||||||
/// It is strongly recommended to use [`Arc::unwrap_or_drop`] instead if you don't | ||||||
/// want to keep the `Arc` in the [`Err`] case. | ||||||
/// Immediately dropping the [`Err`] payload, like in the expression | ||||||
/// `Arc::try_unwrap(this).ok()`, can still cause the strong count to | ||||||
/// drop to zero and the inner value of the `Arc` to be dropped: | ||||||
/// For instance if two threads execute this expression in parallel, then | ||||||
/// there is a race condition. The threads could first both check whether they | ||||||
/// have the last clone of their `Arc` via `try_unwrap`, and then | ||||||
/// both drop their `Arc` in the call to [`ok`][`Result::ok`], | ||||||
/// taking the strong count from two down to zero. | ||||||
/// | ||||||
/// # Examples | ||||||
/// | ||||||
/// ``` | ||||||
|
@@ -500,6 +511,123 @@ impl<T> Arc<T> { | |||||
Ok(elem) | ||||||
} | ||||||
} | ||||||
|
||||||
/// Returns the inner value, if the `Arc` has exactly one strong reference. | ||||||
/// | ||||||
/// Otherwise, [`None`] is returned and the `Arc` is dropped. | ||||||
/// | ||||||
/// This will succeed even if there are outstanding weak references. | ||||||
/// | ||||||
/// If `unwrap_or_drop` is called on every clone of this `Arc`, | ||||||
/// it is guaranteed that exactly one of the calls returns the inner value. | ||||||
/// This means in particular that the inner value is not dropped. | ||||||
/// | ||||||
/// The similar expression `Arc::try_unwrap(this).ok()` does not | ||||||
/// offer such a guarantee. See the last example below and the documentation | ||||||
/// of [`try_unwrap`][`Arc::try_unwrap`]. | ||||||
/// | ||||||
/// # Examples | ||||||
/// | ||||||
/// Minimal example demonstrating the guarantee that `unwrap_or_drop` gives. | ||||||
/// ``` | ||||||
/// #![feature(unwrap_or_drop)] | ||||||
/// | ||||||
/// use std::sync::Arc; | ||||||
/// | ||||||
/// let x = Arc::new(3); | ||||||
/// let y = Arc::clone(&x); | ||||||
/// | ||||||
/// // Two threads calling `unwrap_or_drop` on both clones of an `Arc`: | ||||||
/// let x_unwrap_thread = std::thread::spawn(|| Arc::unwrap_or_drop(x)); | ||||||
/// let y_unwrap_thread = std::thread::spawn(|| Arc::unwrap_or_drop(y)); | ||||||
/// | ||||||
/// let x_unwrapped_value = x_unwrap_thread.join().unwrap(); | ||||||
/// let y_unwrapped_value = y_unwrap_thread.join().unwrap(); | ||||||
/// | ||||||
/// // One of the threads is guaranteed to receive the inner value: | ||||||
/// assert!(matches!( | ||||||
/// (x_unwrapped_value, y_unwrapped_value), | ||||||
/// (None, Some(3)) | (Some(3), None) | ||||||
/// )); | ||||||
/// // The result could also be `(None, None)` if the threads called | ||||||
/// // `Arc::try_unwrap(x).ok()` and `Arc::try_unwrap(y).ok()` instead. | ||||||
/// ``` | ||||||
/// | ||||||
/// A more practical example demonstrating the need for `unwrap_or_drop`: | ||||||
/// ``` | ||||||
/// #![feature(unwrap_or_drop)] | ||||||
/// | ||||||
/// use std::sync::Arc; | ||||||
/// | ||||||
/// // Definition of a simple singly linked list using `Arc`: | ||||||
/// #[derive(Clone)] | ||||||
/// struct LinkedList<T>(Option<Arc<Node<T>>>); | ||||||
/// struct Node<T>(T, Option<Arc<Node<T>>>); | ||||||
/// | ||||||
/// // Dropping a long `LinkedList<T>` relying on the destructor of `Arc` | ||||||
/// // can cause a stack overflow. To prevent this, we can provide a | ||||||
/// // manual `Drop` implementation that does the destruction in a loop: | ||||||
/// impl<T> Drop for LinkedList<T> { | ||||||
/// fn drop(&mut self) { | ||||||
/// let mut x = self.0.take(); | ||||||
/// while let Some(arc) = x.take() { | ||||||
/// Arc::unwrap_or_drop(arc).map(|node| x = node.1); | ||||||
/// } | ||||||
/// } | ||||||
/// } | ||||||
/// | ||||||
/// // implementation of `new` and `push` omitted | ||||||
/// impl<T> LinkedList<T> { | ||||||
/// /* ... */ | ||||||
/// # fn new() -> Self { | ||||||
/// # LinkedList(None) | ||||||
/// # } | ||||||
/// # fn push(&mut self, x: T) { | ||||||
/// # self.0 = Some(Arc::new(Node(x, self.0.take()))); | ||||||
/// # } | ||||||
/// } | ||||||
/// | ||||||
/// // The following code could still cause a stack overflow | ||||||
/// // despite the manual `Drop` impl if that `Drop` impl used | ||||||
/// // `Arc::try_unwrap(arc).ok()` instead of `Arc::unwrap_or_drop(arc)`. | ||||||
/// { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the extra-indentation and block are necessary here, you should be able to put everything at the same level. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not necessary, I know. I felt like it looked better this way, especially making clear that the comment is referencing the entire following code. I also wrote this when the two examples where not split yet. I have not reevaluated if it looks a bit less confusing to have this block on top level now that the examples are split. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can put a vertical space between the overarching comment and the rest: /// // The following code could still cause a stack overflow
/// // despite the manual `Drop` impl if that `Drop` impl used
/// // `Arc::try_unwrap(arc).ok()` instead of `Arc::unwrap_or_drop(arc)`.
/// other comments / code
The example end with this block so there should be no confusion about it. (I hope) |
||||||
/// // create a long list and clone it | ||||||
/// let mut x = LinkedList::new(); | ||||||
/// for i in 0..100000 { | ||||||
/// x.push(i); // adds i to the front of x | ||||||
/// } | ||||||
/// let y = x.clone(); | ||||||
/// | ||||||
/// // drop the clones in parallel | ||||||
/// let t1 = std::thread::spawn(|| drop(x)); | ||||||
/// let t2 = std::thread::spawn(|| drop(y)); | ||||||
/// t1.join().unwrap(); | ||||||
/// t2.join().unwrap(); | ||||||
/// } | ||||||
/// ``` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This second long example feels slightly overkill for this kind of highly specialized method. I would assume that people reaching for this method don't need a motivating real world example anymore. However, now that it's here already, you don't need to remove it. Maybe it helps someone after all ^_^ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I know, I called it overkill myself. Nontheless, more examples aren’t going to hurt, probably. |
||||||
#[inline] | ||||||
#[unstable(feature = "unwrap_or_drop", issue = "none")] // FIXME: add issue | ||||||
// FIXME: should this copy all/some of the comments from drop and drop_slow? | ||||||
pub fn unwrap_or_drop(this: Self) -> Option<T> { | ||||||
// following the implementation of `drop` (and `drop_slow`) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comments in std usually start uppercase. This also applies to other comments that I won't individually comment on.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I’ll still have to rework the comments a bit anyways, as suggested above. But thanks for the info anyways. |
||||||
let mut this = core::mem::ManuallyDrop::new(this); | ||||||
|
||||||
if this.inner().strong.fetch_sub(1, Release) != 1 { | ||||||
return None; | ||||||
} | ||||||
|
||||||
acquire!(this.inner().strong); | ||||||
|
||||||
// FIXME: should the part below this be moved into a seperate #[inline(never)] | ||||||
// function, like it's done with drop_slow in drop? | ||||||
Comment on lines
+621
to
+622
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is necessary for now. We can still improve this later, if it seems useful. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was mostly just curious what the reasoning for the separation in the |
||||||
|
||||||
// using `ptr::read` where `drop_slow` was using `ptr::drop_in_place` | ||||||
let inner = unsafe { ptr::read(Self::get_mut_unchecked(&mut this)) }; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I forgot: Please write a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The situation is a bit more complex. It is safe because it is doing the same things that the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I've done before is say something along the lines of "this is safe for the same reason the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I didn't answer earlier, I didn't get the notification for this comment. The comments in the You can add a line about the differences between |
||||||
|
||||||
drop(Weak { ptr: this.ptr }); | ||||||
|
||||||
Some(inner) | ||||||
} | ||||||
} | ||||||
|
||||||
impl<T> Arc<[T]> { | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.