| Crates.io | safe-manually-drop |
| lib.rs | safe-manually-drop |
| version | 0.1.0 |
| created_at | 2025-07-12 09:39:32.549843+00 |
| updated_at | 2025-07-14 23:43:15.763525+00 |
| description | `ManuallyDrop` "owned field" pattern with no `unsafe`, no `.unwrap()`s, no macros |
| homepage | |
| repository | https://github.com/danielhenrymantilla/safe-manually-drop.rs |
| max_upload_size | |
| id | 1749166 |
| size | 101,291 |
::safe-manually-dropConvenience 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 wrapped
field: SafeManuallyDrop<FieldTy, Self>,
Deref{,Mut}, as well as
From::from() and ".into()"
conversions.)then, provide the companion, mandatory,
impl DropManually<FieldTy> for ContainingType {
Profit™ (from the owned access to FieldTy inside of DropManually::drop_manually()'s body).
.into_inner_defusing_impl_Drop()
which shall "deconstruct" that FieldTy despite the DropManually impl
(which shall get defused).)Available over the relevant section.
DropConsider, for instance, the two following examples:
DeferThis 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:
# /*
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 typeImagine 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.
# mod some_lib {
# pub struct Transaction {}
# impl Transaction {
# pub fn commit(self) {}
# pub fn roll_back(self) {}
# }
# }
#
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:
# mod some_lib {
# pub struct Transaction {}
# impl Transaction {
# pub fn commit(self) {}
# pub fn roll_back(self) {}
# }
# }
#
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:
# /*
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.
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…).)Option-{un,}wrapping the fieldThe 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.
Deferfn 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# mod some_lib {
# pub struct Transaction {}
# impl Transaction {
# pub fn commit(self) {}
# pub fn roll_back(self) {}
# }
# }
#
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();
};
}
}
unsafe-ly ManuallyDrop-wrapping the fieldThe 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 ⚠️😱⚠️
Deferfn 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# mod some_lib {
# pub struct Transaction {}
# impl Transaction {
# pub fn commit(self) {}
# pub fn roll_back(self) {}
# }
# }
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)?
SafeManuallyDrop and DropManuallyThis 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:
Deferfn 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# mod some_lib {
# pub struct Transaction {}
# impl Transaction {
# pub fn commit(self) {}
# pub fn roll_back(self) {}
# }
# }
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à 😙👌
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.