1 unstable release
0.1.0 | Dec 27, 2020 |
---|
#210 in No standard library
1,941 downloads per month
32KB
463 lines
drop-move
This crate allows implementing Drop
as "pass by move". Here is an example of how this can be
used to call a FnOnce
from drop
.
use drop_move::{drop_move_wrap, DropMove, DropHandle};
drop_move_wrap! {
/// Runs a function when dropped.
#[derive(Clone)]
pub struct DropGuard<F: FnOnce()>(DropGuardInner {
func: F,
});
}
impl<F: FnOnce()> DropMove for DropGuardInner<F> {
fn drop_move(self_: DropHandle<Self>) {
(DropHandle::into_inner(self_).func)()
}
}
impl<F: FnOnce()> DropGuard<F> {
pub fn new(f: F) -> Self {
DropGuardInner { func: f }.into()
}
}
let mut x: u32 = 0;
{
let y = Box::new(&mut x); // Box is not Copy, so the closure will only be FnOnce.
let guard = DropGuard::new(move || **y += 1);
}
assert_eq!(x, 1);
By implementing the DropMove
trait, we were able to have func
run when the DropGuard
goes
out of scope. The usual Drop
trait only allows drop(&mut self)
, which does not allow moving
the members of the DropGuard
, as is required to call a FnOnce
. The reason they do not allow
drop(self)
is that it would be too easy to accidentally end up dropping self
, leading to an
infinite loop. According to Rust's usual semantics self
would be dropped at the end of the scope,
and even if a special case were added for drop(self)
it could still easily happen in a function
called by drop
.
These problems are mostly avoided by wrapping self
in a DropHandle
, which will only drop each
member of the structure when it goes out of scope, rather than calling drop
recursively.
Semantically, drop_move
can be thought of as destructuring DropGuard
.
Each unmoved member will be dropped when it goes out of scope. These members can be accessed by
value through into_inner
. The original DropGuard
can be obtained from
into_outer
, but you must be careful to avoid infinite recursion when
using this.
Given this destructuring viewpoint, it should be no surprise that drop_move
also supports
destructuring, which is normally not allowed for types that implement Drop
. Here, we can
convert the DropGuard
back into the function it contains.
impl<F: FnOnce()> DropGuard<F> {
/// Extract the function.
pub fn into_inner(self) -> F {
let inner: DropGuardInner<F> = self.into();
inner.func
}
}
How this works is that drop_move_wrap!
expands into two structure definitions.
/// Runs a function when dropped.
#[derive(Clone)]
pub struct DropGuard<F: FnOnce()>(DropMoveWrapper<DropGuardInner<F>>);
/// Runs a function when dropped.
#[derive(Clone)]
struct DropGuardInner<F: FnOnce()> {
func: F,
};
The outer structure DropGuard
provides the public interface, while DropGuardInner
contains the
actual members of the struct
. Neither will implement Drop
. Instead, DropMoveWrapper
will
implement Drop
based on the DropMove
you provide. The structure members can be borrowed
from a DropGaurd
using &self.0.func
, because DropMoveWrapper
implements Deref
. They can
moved by converting the DropGaurd
to a DropGuardInner
with
DropMoveWrapper::into_inner(self.0)
or
self.into()
.
Notice that the doc comments and attributes have been duplicated for both structures. In fact, doc comments are treated as attributes by the compiler.
The macro also creates a few trait implementations.
impl<F: FnOnce()> From<DropGuard<F>> for DropGuardInner<F> {
fn from(x: DropGuard<F>) -> Self {
DropMoveWrapper::into_inner(x.0)
}
}
impl<F: FnOnce()> From<DropGuardInner<F>> for DropGuard<F> {
fn from(x: DropGuardInner<F>) -> Self {
Self(DropMoveWrapper::new(x))
}
}
impl<F: FnOnce()> DropMoveTypes for DropGuardInner<F>
{
type Outer = DropGuard<F>;
}
The implementation of DropMoveTypes
lets DropMoveWrapper
and DropHandle
know the
relationship between DropGuard
and DropGuardInner
. It is implemented on the inner structure
because this will keep the implementation private in the common case that the inner structure is
private but the outer is public. The From
implementations are so that they know how to convert
back and forth, and also function as convenience methods for creating and destructuring
DropGuard
s.
You may be wondering why drop_move
takes a DropHandle
rather than just passing the inner
structure DropGuardInner
, which would behave correctly for destructuring and would drop the
members individually. However, you wouldn't easily be able to call a &self
or &mut self
function, which would want an instance of DropGuard
instead. It would require reconstructing the
DropGuard
again so that it can be borrowed, then carefully destructuring it after the call to
avoid infinite drop
recursion. DropHandle
allows you to avoid this error prone construction
as it implements Deref
for the outer structure, so you can call its methods directly.
See drop_move_wrap!
's docs for the macro's full supported syntax. See the source for DropGuard
for the full example.