Skip to content
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

Override Waker::clone_from to avoid cloning Wakers unnecessarily #96979

Merged
merged 1 commit into from
Nov 5, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion library/core/src/task/wake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,10 @@ impl fmt::Debug for Context<'_> {
/// this might be done to wake a future when a blocking function call completes on another
/// thread.
///
/// Note that it is preferable to use `waker.clone_from(&new_waker)` instead
/// of `*waker = new_waker.clone()`, as the former will avoid cloning the waker
/// unnecessarily if the two wakers [wake the same task](Self::will_wake).
///
/// [`Future::poll()`]: core::future::Future::poll
/// [`Poll::Pending`]: core::task::Poll::Pending
#[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/66401
Expand Down Expand Up @@ -302,7 +306,9 @@ impl Waker {
/// when the `Waker`s would awaken the same task. However, if this function
/// returns `true`, it is guaranteed that the `Waker`s will awaken the same task.
///
/// This function is primarily used for optimization purposes.
/// This function is primarily used for optimization purposes — for example,
/// this type's [`clone_from`](Self::clone_from) implementation uses it to
/// avoid cloning the waker when they would wake the same task anyway.
#[inline]
#[must_use]
#[stable(feature = "futures_api", since = "1.36.0")]
Expand Down Expand Up @@ -382,6 +388,13 @@ impl Clone for Waker {
waker: unsafe { (self.waker.vtable.clone)(self.waker.data) },
}
}

#[inline]
fn clone_from(&mut self, source: &Self) {
Copy link
Contributor

@coolreader18 coolreader18 Nov 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure if it'd make sense to do this here or another PR, but it might be good to add docs here to aid discoverability (I believe rustdoc pops open trait impls by default when an associated item has docs? or at least it'll catch the eye when scanning the page), and also especially since the will_wake docs link here. I can't remember it off the top of my head, but I'm almost certain there's prior art in the standard library of adding docs to associated items of an impl.

Copy link
Member

@workingjubilee workingjubilee Nov 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can handle that in a followup. It might be a good idea to doc ~all the overridden clone_froms in std as a single PR.

if !self.will_wake(source) {
*self = source.clone();
}
}
Comment on lines +392 to +397
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally, Waker runs clone side effects. This, in some cases, avoids doing so. This seems like observable behavior. Are we okay with changing it? Are there implementations "in the wild" that will be affected negatively by this? Normally, clone_from is opt-in, so I think it's totally reasonable to have it differ slightly from the naive case of clone... this is its stated purpose, after all.

Copy link
Contributor

@a1phyr a1phyr Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes how the code is executed, but not its semantics. Like String::clone_from can avoid allocating, but the semantic result compared to *self = other.clone() is the same.

Moreover, I can't imagine any sane code where this breaks something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can potentially have an arbitrary side-effect, like printing an integer, but of course I think ruling that as a fundamentally unserious use-case (or at least, easily circumvented by not calling clone_from) seems reasonable.

}

#[stable(feature = "futures_api", since = "1.36.0")]
Expand Down