-
-
Notifications
You must be signed in to change notification settings - Fork 14.4k
Add convenient constructor for building DropGuard<(), FnOnce(())>
#150027
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
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: tison <[email protected]>
library/core/src/mem/drop_guard.rs
Outdated
| /// { | ||
| /// // Create a new guard that will do something | ||
| /// // when dropped. | ||
| /// let _guard = DropGuard::new(|()| println!("Goodbye, world!")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /// let _guard = DropGuard::new(|()| println!("Goodbye, world!")); | |
| /// let _guard = DropGuard::new(|| println!("Goodbye, world!")); |
It's more proper to write || without (). But it seems quite hard to fine-tune the type parameter F: FnOnce(T) even if we are sure that T = (). impl FnOnce(()) is different from impl FnOnce().
|
I think the proposed API would be nice to have if it got rid of the unit argument, but as it is it feels like a half-measure. I don't know if it would be popular, but maybe we could provide a macro_rules! defer {
($($t:tt)*) => {
let _guard = DropGuard::new((), |()| { $($t)* });
};
}This works too: const fn defer<F: FnOnce()>(f: F) -> DropGuard<(), impl FnOnce(())> {
DropGuard {
inner: ManuallyDrop::new(()),
f: ManuallyDrop::new(move |()| f()),
}
} |
This looks like a good solution. Let me try it out. So far we export |
Signed-off-by: tison <[email protected]>
|
Updated with: Maybe we should keep |
|
I think we should keep The macro is not perfect, e.g. I don't think you can have both |
Signed-off-by: tison <[email protected]>
Signed-off-by: tison <[email protected]>
|
@nxsaken Great suggestion! Updated. |
library/core/src/mem/drop_guard.rs
Outdated
| /// ``` | ||
| #[unstable(feature = "drop_guard", issue = "144426")] | ||
| pub macro defer($($t:tt)*) { | ||
| $crate::mem::DropGuard::new((), |()| { $($t)* }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if |()| { ... } can proper move value when needed.
Added defer_moved_value test case and it seems to work properly.
std::mem::defer for conveniently build DropGuard<(), FnOnce(())>
|
r? libs-api |
Signed-off-by: tison <[email protected]>
|
I'd lean toward keeping If we go with a macro, one advantage of a macro is that it can wrap the entire |
|
I think the macro makes a lot of sense, if it includes the Any usage pattern where the lifetime of the guards ends early in some branch via |
|
5 out of 10 in my use cases need to store the |
Do the closures in that case not capture anything from their environment? Or are you boxing them / using the unstable type-alias-impl-trait to be able to name the Something like this should be possible, but the default on pub struct DropGuard<T = (), F = UnitFn<fn()>>
where
F: FnOnce(T),
{
inner: ManuallyDrop<T>,
f: ManuallyDrop<F>,
}
#[unstable(...)] // perma-unstable
struct UnitFn<F>(F);
impl<F> FnOnce<((),)> for UnitFn<F>
where
F: FnOnce<()>,
{
type Output = <F as FnOnce<Args>>::Output;
extern "rust-call" fn call_once(self, _: ((),)) -> Self::Output {
<F as FnOnce<Args>>::call_once(self.0, ())
}
}
impl<F> DropGuard<(), UnitFn<F>>
where
F: FnOnce(),
{
pub const fn new(f: F) -> Self {
Self {
inner: ManuallyDrop::new(()),
f: ManuallyDrop::new(UnitFn(f)),
}
}
}
impl<T, F> DropGuard<T, F>
where
F: FnOnce(T),
{
pub const fn with_value(value: T, f: F) -> Self {
// ...
}
}
// plus the deref macro including the `let _guard` bit, for convenience |
|
@jplatte Sounds reasonable. Let me give it a try tomorrow. |
5ffca89 to
9dcc0e6
Compare
This comment has been minimized.
This comment has been minimized.
Signed-off-by: tison <[email protected]>
9dcc0e6 to
6f03732
Compare
|
@jplatte Updated. |
| } | ||
| } | ||
|
|
||
| #[unstable(feature = "drop_guard", issue = "144426")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be permanently unstable. But we don't export it anyway.
Co-authored-by: Jonas Platte <[email protected]>
| #[doc(alias = "ScopeGuard")] | ||
| #[doc(alias = "defer")] | ||
| pub struct DropGuard<T, F> | ||
| pub struct DropGuard<T = (), F = UnitFn<fn()>> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that UnitFn<fn()> wraps a function pointer. Closures are monomorphized/inlined, function pointers are not. You still have to write out the whole type if you want to store a guard with an actual closure, but that would either leak UnitFn into the public API, or it would prevent using the ergonomic constructor.
struct Struct<Fn0, Fn1>
where
Fn0: FnOnce(()),
Fn1: FnOnce(),
{
good: DropGuard<(), Fn0>,
bad: DropGuard<(), UnitFn<Fn1>>, // ERROR: unstable UnitFn
bad2: DropGuard, // stores a function pointer!
}
impl<Fn0, Fn1> Struct<Fn0, Fn1>
where
Fn0: FnOnce(()),
Fn1: FnOnce(),
{
fn new(fn0: Fn0, fn1: Fn1) -> Self {
Self {
// ERROR: expected FnOnce(), found FnOnce(())
good: DropGuard::new(fn1),
bad: DropGuard::new(fn1),
// ERROR: expected fn(), found FnOnce()
bad2: DropGuard::new(fn1),
}
}
}DropGuard::new((), |_| { ... }) seems to be the optimal API for the standard library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I see it, this is unrelated to the entire discussion above.
Yes, with current Rust you can not name a DropGuard that holds a closure, without coercing that closure into a function pointer (which only works if that closure doesn't capture anything).
However, DropGuard::new as this PR has it does not always return a DropGuard<(), UnitFn<fn()>>. If type inference doesn't force the passed FnOnce to be coerced to a fn pointer, it won't be.
DropGuard::new only gets rid of the weird unit parameter, that's it. And the default for F only allows naming the type of DropGuard obtained from DropGuard::new (at the expense of the function pointer indirection), nothing more or less.
Co-authored-by: Jonas Platte <[email protected]>
std::mem::defer for conveniently build DropGuard<(), FnOnce(())>DropGuard<(), FnOnce(())>
|
cc @yoshuawuyts (due to being the issue author) |
|
r? libs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not sure whether this change carries its weight. It makes the main intended use case of this API harder by making main constructor harder to remember. DropGuard::new is pretty obvious, DropGuard::with_value does not have any precedent in the stdlib, so people will need to either memorize it or look it up:
let _guard = DropGuard::new(x, |x| ..); // ← current
let _guard = DropGuard::with_value(x, |x| ..); // ← proposedI would like to see evidence presented that constructing DropGuard without any values is so common that we would want to make it the default. In rust-lang/rust we have a total of 42 references of the DropGuard pattern, of which 7 don’t take any arguments (ref). That’s a hit rate of 16%, which to me seems too low to change the default. Especially since that 16% of cases is already supported today by writing:
let _guard = DropGuard::new((), |_| ..);Perhaps there is a way to introduce a convenience function that doesn’t change the default? For example something like DropGuard::with_default that defaults to passing (). But that’s something which could always be considered later.
| /// println!("Goodbye, world!"); | ||
| /// } | ||
| /// ``` | ||
| #[unstable(feature = "drop_guard", issue = "144426")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a new feature which should not be tracked as part of the drop guard tracking issue. I'd also suggest filing this in a separate PR, since introducing this macro is a different question from whether we should add this convenience function.
| } | ||
|
|
||
| #[unstable(feature = "drop_guard", issue = "144426")] | ||
| pub struct UnitFn<F>(F); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how this works. Since this is public, I would expected to be documented. Probably extra so since the expectation is to keep this perma-unstable.
|
An alternative is to implement the convenient constructor as a free function: #![feature(drop_guard)]
use std::mem::DropGuard;
fn main() {
let drop_guard = drop_guard(|| {
println!("Dropped!");
});
println!("Doing some work...");
}
fn drop_guard(f: impl FnOnce()) -> DropGuard<(), impl FnOnce(())> {
DropGuard::new((), |()| f())
}But the FQN is hard to decide in this case. |
I suppose this is a bias in the stdlib where we test many cases that having a value. In the broader ecosystem, this DropGuard pattern is commonly used as a defer block. |
|
While I agree there is likely a bias, perhaps that's best for the tracking issue? Up to you, as you're the PR author. |
This refers to #144426.
cc @andylokandy @jplatte
Updated with:
#150027 (comment)
.. plus:
Unfortunately, it's hard to convert
impl FnOnce()to the input type paramF: FnOnce(T) where T = ().I tried:
But it failed because the new closure has a different type of
F.