Expand description
§::safe-manually-drop
Convenience wrapper type —and trait
!— to expose owned access to a field when customizing the drop
glue of your type.
Non-macro equivalent of
::drop_with_owned_fields
.
To expose owned access to a FieldTy
when drop glue is being run, this crate offers a handy,
0-runtime-overhead, non-unsafe
, tool:
-
Use, instead of a
field: FieldTy
, a wrappedfield: SafeManuallyDrop<FieldTy, Self>
,- (This wrapper type offers transparent
Deref{,Mut}
, as well asFrom::from()
and “.into()
” conversions.)
- (This wrapper type offers transparent
-
then, provide the companion, mandatory,
impl DropManually<FieldTy> for ContainingType {
-
Profit™ (from the owned access to
FieldTy
inside ofDropManually::drop_manually()
’s body).- (and also from the convenience
.into_inner_defusing_impl_Drop()
which shall “deconstruct” thatFieldTy
despite theDropManually
impl (which shall get defused).)
- (and also from the convenience
§Examples
Available over the relevant section.
§Motivation: owned access to some field(s) on Drop
Click to hide
Consider, for instance, the two following examples:
§Defer
This is basically a simpler ::scopeguard::ScopeGuard
. The idea is that you’d first want to
(re)invent some kind of defer! { … }
mechanism via an ad-hoc impl Drop
type:
// desired usage:
fn example() {
let _deferred = defer(|| {
println!("Bye, world!");
});
println!("Hello, world!");
// stuff… (even stuff that may panic!)
} // <- *finally* / either way, `Bye` is printed here.
Here is how we could implement it:
fn defer(f: impl FnOnce()) -> impl Drop {
return Wrapper(f);
// where:
struct Wrapper<F : FnOnce()>(F);
impl<F : FnOnce()> Drop for Wrapper<F> {
fn drop(&mut self) {
self.0() // Error, cannot move out of `self`, which is behind a `&mut` reference.
}
}
}
But this fails to compile! Indeed, since Drop
only exposes &mut self
access on drop()
, we only
get &mut
access to the closure, so the closure can only, at most, be an FnMut()
, not an
FnOnce()
.
-
Error message:
Click to show
ⓘerror[E0507]: cannot move out of `self` which is behind a mutable reference --> src/_lib.rs:44:13 | 10 | self.0() // Error, cannot move out of `self`, which is behind a `&mut` reference. | ^^^^^^-- | | | `self.0` moved due to this call | move occurs because `self.0` has type `F`, which does not implement the `Copy` trait | note: this value implements `FnOnce`, which causes it to be moved when called --> src/_lib.rs:44:13 | 10 | self.0() // Error, cannot move out of `&mut` reference. | ^^^^^^
So we either have to forgo using FnOnce()
here, and settle for a limited API, such as
F : FnMut()
(as in, more limited than what we legitimately know we should be able to soundly
have here: FnOnce()
). Or we have to find a way to get owned access on drop to our F
field.
Another example of this problem would be the case of:
§rollback
-on-Drop
transaction wrapper type
Imagine having to deal with the following API:
mod some_lib {
pub struct Transaction {
// private fields…
}
// owned access in these methods for a stronger, type-state-based, API.
impl Transaction {
pub fn commit(self) {
// …
}
pub fn roll_back(self) {
// …
}
}
// say this does not have a default behavior on `Drop`,
// or one which we wish to override.
}
We’d now like to have our own WrappedTransaction
type, wrapping this API, with the added
feature / functionality of it automagically rolling back the transaction when implicitly dropped
(e.g., so that ?
-bubbled-up errors and panics trigger this rollback path), expecting the users
to explicitly .commit()
it at the end of their happy paths.
struct WrappedTransaction(some_lib::Transaction);
impl WrappedTransaction {
fn commit(self) {
self.0.commit(); // OK
}
}
// TODO: Add `roll_back` on `Drop`
If we go with the naïve approach, we’d end up doing:
struct WrappedTransaction(some_lib::Transaction);
// 👇
impl Drop for WrappedTransaction {
fn drop(&mut self) {
// 💥 Error, cannot move out of `self`, which is behind `&mut`,
// yadda yadda.
self.0.roll_back();
}
}
impl WrappedTransaction {
fn commit(self) {
// Not only that, but we now also get the following extra error:
//
// 💥 Error cannot move out of type `WrappedTransaction`,
// which implements the `Drop` trait
self.0.commit();
}
}
-
Error message:
Click to show
ⓘerror[E0507]: cannot move out of `self` which is behind a mutable reference --> src/_lib.rs:162:9 | 16 | self.0.roll_back(); | ^^^^^^ ----------- `self.0` moved due to this method call | | | move occurs because `self.0` has type `Transaction`, which does not implement the `Copy` trait | note: `Transaction::roll_back` takes ownership of the receiver `self`, which moves `self.0` --> src/_lib.rs:153:26 | 7 | pub fn roll_back(self) {} | ^^^^ note: if `Transaction` implemented `Clone`, you could clone the value --> src/_lib.rs:150:5 | 4 | pub struct Transaction {} | ^^^^^^^^^^^^^^^^^^^^^^ consider implementing `Clone` for this type ... 16 | self.0.roll_back(); | ------ you could clone this value error[E0509]: cannot move out of type `WrappedTransaction`, which implements the `Drop` trait --> src/_lib.rs:171:9 | 25 | self.0.commit(); | ^^^^^^ | | | cannot move out of here | move occurs because `self.0` has type `Transaction`, which does not implement the `Copy` trait | note: if `Transaction` implemented `Clone`, you could clone the value --> src/_lib.rs:150:5 | 4 | pub struct Transaction {} | ^^^^^^^^^^^^^^^^^^^^^^ consider implementing `Clone` for this type ... 25 | self.0.commit(); | ------ you could clone this value
The first error is directly related to the lack of owned access, and instead, the limited
&mut self
access, which the Drop
trait exposes in its fn drop(&mut self)
function.
- (and the second error is a mild corollary from it, as in, the only way to extract owned access
to a field of a
struct
would be by deconstructing it, which would entail defusing its extra/prepended drop glue, and that is something which Rust currently conservatively rejects (hard error, rather than some lint or whatnot…).)
§How rustaceans currently achieve owned access in drop
§Either Option
-{un,}wrap
ping the field
The developer would wrap the field in question in an Option
, expected to always be Some
for
the lifetime of every instance, but for those last-breath/deathrattle moments in Drop
, wherein the
field can then be .take()
n behind the &mut
, thereby exposing, if all the surrounding code
played ball, owned access to that field.
Should some other code have a bug w.r.t. this property, the .take()
would yield None
, and a
panic!
would ensue.
§Defer
fn defer(f: impl FnOnce()) -> impl Drop {
return Wrapper(Some(f));
// +++++ +
// where:
struct Wrapper<F : FnOnce()>(Option<F>);
// +++++++ +
impl<F : FnOnce()> Drop for Wrapper<F> {
fn drop(&mut self) {
self.0.take().expect("🤢")()
// +++++++++++++++++++
}
}
}
§Transaction
struct WrappedTransaction(Option<some_lib::Transaction>);
// +++++++ +
impl Drop for WrappedTransaction {
fn drop(&mut self) {
self.0.take().expect("🤢").roll_back();
// ++++++++++++++++++++
}
}
impl WrappedTransaction {
/// 👇 overhauled.
fn commit(self) {
let mut this = ::core::mem::ManuallyDrop::new(self);
if true {
// naïve, simple, approach (risk of leaking *other* fields (if any))
let txn = this.0.take().expect("🤢");
txn.commit();
} else {
// better approach (it does yearn for a macro):
let (txn, /* every other field here */) = unsafe { // 😰
(
(&raw const this.0).read(),
// every other field here
)
};
txn.expect("🤢").commit();
};
}
}
§Or unsafe
-ly ManuallyDrop
-wrapping the field
The developer would wrap the field in question in a ManuallyDrop
, expected never to have been
ManuallyDrop::drop()
ped already for the lifetime of every instance, but for those
last-breath/deathrattle moments in Drop
, wherein the field can then be ManuallyDrop::take()
n
behind the &mut
, thereby exposing, if all the surrounding code played ball, owned access to that
field.
Should some other code have a bug w.r.t. this property, the ManuallyDrop::take()
would be
accessing a stale/dropped value, and UB would be very likely to ensue ⚠️😱⚠️
§Defer
fn defer(f: impl FnOnce()) -> impl Drop {
return Wrapper(ManuallyDrop::new(f));
// ++++++++++++++++++ +
// where:
use ::core::mem::ManuallyDrop; // 👈
struct Wrapper<F : FnOnce()>(ManuallyDrop<F>);
// +++++++++++++ +
impl<F : FnOnce()> Drop for Wrapper<F> {
fn drop(&mut self) {
unsafe { // 👈 😰
ManuallyDrop::take(&mut self.0)()
// ++++++++++++++++++
}
}
}
}
§Transaction
use ::core::mem::ManuallyDrop; // 👈
struct WrappedTransaction(ManuallyDrop<some_lib::Transaction>);
// +++++++++++++ +
impl Drop for WrappedTransaction {
fn drop(&mut self) {
unsafe { // 😰
ManuallyDrop::take(&mut self.0).roll_back();
// +++++++++++++++++++ +
}
}
}
impl WrappedTransaction {
/// 👇 overhauled.
fn commit(self) {
let mut this = ::core::mem::ManuallyDrop::new(self);
if true {
// naïve, simple, approach (risk of leaking *other* fields (if any))
let txn = unsafe {
ManuallyDrop::take(&mut this.0)
};
txn.commit();
} else {
// better approach (it does yearn for a macro):
let (txn, /* every other field here */) = unsafe { // 😰
(
(&raw const this.0).read(),
// every other field here
)
};
ManuallyDrop::into_inner(txn).commit();
};
}
}
Both of these approaches are unsatisfactory, insofar the type system does not prevent implementing
this pattern incorrectly: bugs remain possible, leading to either crashes in the former
non-unsafe
case, or to straight up UB in the latter unsafe
case.
Can’t we do better? Doesn’t the Drop
trait with its meager &mut self
grant appear to be the
culprit here? What if we designed a better trait (with, potentially, helper types)?
§Enter this crate: SafeManuallyDrop
and DropManually
This is exactly what the DropManually
trait fixes: by being more clever about the signature of its
own “dropping function”, it is able to expose, to some implementor type, owned access to one of its
(aptly wrapped) fields:
§Defer
fn defer(f: impl FnOnce()) -> impl Sized {
return Wrapper(SafeManuallyDrop::new(f));
// where:
use ::safe_manually_drop::{SafeManuallyDrop, DropManually}; // 👈
// 👇 1. instead of the `Drop` trait, use:
impl<F : FnOnce()> DropManually<F> for Wrapper<F> {
fn drop_manually(f: F) {
// It is *that simple*, yes!
f();
}
}
// 2. `SafeManuallyDrop` shall use it on `Drop`
struct Wrapper<F : FnOnce()>(SafeManuallyDrop<F, Self>);
// +++++++++++++++++ +++++++
}
§Transaction
use ::safe_manually_drop::{DropManually, SafeManuallyDrop};
struct WrappedTransaction(SafeManuallyDrop<some_lib::Transaction, Self>);
// +++++++++++++++++ +++++++
impl DropManually<some_lib::Transaction> for WrappedTransaction {
fn drop_manually(txn: some_lib::Transaction) {
// It is *that simple*, yes!
txn.roll_back();
}
}
impl WrappedTransaction {
fn commit(self) {
// It is *that friggin' simple*, yes! (no risk to leak the other fields 🤓)
let txn = self.0.into_inner_defusing_impl_Drop();
txn.commit();
}
}
And voilà 😙👌
§Addendum: Drop impl
vs. drop glue vs. drop()
It is generally rather important to properly distinguish between these three notions, but especially so in the context of this crate!
Only skip this section if you can confidently answer what drop
means in the context of:
trait Drop { fn drop(&mut self); }
mem::drop::<T>(…);
ptr::drop_in_place::<T>(…);
mem::needs_drop::<T>();
and if it is obvious to you that String
does not impl Drop
.
Modules§
Structs§
- Safe
Manually Drop SafeManuallyDrop<FieldTy>
is the safe counterpart ofManuallyDrop<FieldTy>
, and the zero-runtime-overhead counterpart ofOption<FieldTy>
.
Traits§
- Drop
Manually - The main/whole point of this whole crate and design: to expose owned access to a
FieldTy
when drop glue is being run.