Crates.io | consume_on_drop |
lib.rs | consume_on_drop |
version | 0.1.1 |
source | src |
created_at | 2023-05-24 03:33:13.008561 |
updated_at | 2023-05-24 03:45:44.124798 |
description | A zero-cost abstraction that allows Drop::drop to consume self by value |
homepage | |
repository | https://github.com/markcsaving/consume_on_drop |
max_upload_size | |
id | 872589 |
size | 21,174 |
consume_on_drop
A zero-cost abstraction that allows Drop::drop to consume self by value
Do you want to call a function that takes self
by value in your impl Drop
? Do you need the ability
to both destructure and drop your struct? Do you want a convenience type to temporarily give your
values a new Drop
implementation? This crate is for you.
ConsumeOnDrop<T>
is a #[repr(transparent)]
wrapper around T
, and all provided operations are zero-cost.
WithConsumer<T, Q>
is a thin wrapper around an ordered pair (T, Q)
, and all its provided operations are zero-cost.
All public functions, methods, and traits in these APIs are completely safe.
unsafe
codeThe implementation of ConsumeOnDrop
has exactly 2 lines of unsafe
code, both easy checked and tested with Miri.
The implementation of WithConsumer
is completely safe (except insofar as it depends on the public API of
ConsumeOnDrop
).
use consume_on_drop::{Consume, ConsumeOnDrop, WithConsumer};
struct T;
fn consume_t(_t: T) {
println!("We consumed T")
}
impl Consume for T {
fn consume(self) {
consume_t(self)
}
}
fn main () {
let t = ConsumeOnDrop::new(T); // A thin wrapper around T which calls T::consume on drop
drop(t);
let t = WithConsumer::new(T, consume_t); // Alternately, we can explicitly equip a T with a consumer.
drop(t);
}
The following code doesn't compile.
struct MutRef<'a> {
inner: &'a mut i32,
}
impl<'a> MuRef<'a> {
pub fn new(val: &'a mut i32) -> Self {
Self { inner: val }
}
pub fn into_inner(self) -> &'a mut i32 {
self.inner
}
}
impl<'a> Drop for MutRef<'a> {
fn drop(&mut self) {
*self.inner += 1;
}
}
but we can make it work using ConsumeOnDrop
:
use consume_on_drop::{Consume, ConsumeOnDrop};
struct RawMut<'a> {
inner: &'a mut i32,
}
impl<'a> Consume for RawMut<'a> {
fn consume(self) {
*self.inner += 1;
}
}
struct MutRef<'a> {
inner: ConsumeOnDrop<RawMut<'a>>,
}
impl<'a> MutRef<'a> {
pub fn new(val: &'a mut i32) -> Self {
Self { inner: ConsumeOnDrop::new(RawMut { inner: val })}
}
pub fn into_inner(self) -> &'a mut i32 {
ConsumeOnDrop::into_inner(self.inner).inner
}
}
Note that this is a zero-cost abstraction. We could achieve the same effect using Option<RawMut>
, but this
incurs run-time overhead and prevents us from using the null-pointer optimization on Option<MutRef>
.
Sometimes, you need to temporarily give your type a new drop
implementation in case you return early or panic.
Often, data may be left in an invalid state if a panic happens at the wrong time. To mark this, you may wish to "poison"
your data.
use consume_on_drop::WithConsumer;
struct Data {
string: Option<String>,
}
impl Data {
fn new(str: String) -> Self {
Self { string: Some(str) }
}
fn extend(&mut self, str: String) {
self.string.as_mut().unwrap().extend(str.chars())
}
fn poison(&mut self) {
self.string = None;
}
}
fn produce_string() -> String {
panic!("Oh no, we panicked!");
}
fn extend_produce(data: &mut Data) {
let mut data = WithConsumer::new(data, Data::poison);
data.extend(produce_string()); // if produce_string panics, we will drop data here and poison it
WithConsumer::into_inner(data); // but if there's no panic, we will not poison.
}
License: MIT license