Yes. And while it works well, it still itches because it requires unsafe
. What's your opinion about adding support for this kind of thing to the compiler? If there's no obvious reason why this wouldn't work, then I'll go on and work out a RFC and try to implement that.
I do not have time to implement it right now (I say as I struggle to prevent myself from going and implementing it), but a #[derive(AfterDrop)]
could be set up for this. Given that a safe abstraction can be built without language support, getting it into the stdlib is going to be an uphill battle if you want to fight for it; the Rust stdlib prefers to stick to fundamental abstractions.
#[derive(AfterDrop)]
struct Guard {
#[after_drop]
resource: ManuallyDrop<Resource>,
trivial: Trivial,
}
// derived
impl Drop for Guard {
fn drop(&mut self) {
let mut resource;
unsafe {
resource = mem::uninitialized();
ptr::copy_nonoverlapping(&*self.resource, &mut resource, 1);
}
Self::after_drop(resource);
}
}
You could probably use frunk
's (Labelled
)Generic
to make AfterDrop<HList![..]>
a trait, but it seems unnecessary. You could make the trait AfterDrop<(..,)>
if you really want it to be a trait; the derive macro can construct the flat tuple (wheras HList
is a cons-list tuple).
Just ptr::read
it:
impl Drop for Guard {
fn drop(&mut self) {
let resource = unsafe { ManuallyDrop::into_inner(ptr::read(&self.resource)) };
do_something_more_with_precious_resource(resource);
}
}
you shouldn't then read it again, but since the only place you're doing this is in drop
, that's easy.
(It might be worth having an unsafe
method for this combination specifically, to make it more obvious.)
Please treat mem::uninitialized()
as deprecated; it will be soon. And ptr::read
is simpler and better here.
(Also, reminder that "swap with a local" is usually better written with mem::replace
.)
The attribute-and-stringly-connected version feels like too much of a hack for me to have great confidence in it getting accepted. I think it would have better luck if it was more like normal behaviour, just a bit special since Drop
is already special.
Imagine, say, that you could do something like this:
impl DropPrime for Guard {
fn drop_prime(Guard { resource }: Self) {
do_something_more_with_precious_resource(resource);
}
}
That's already legal syntax. It correctly drops all the fields if you don't put anything in the body, but it gives you ownership of them to move or forget as you wish. It wouldn't even need the "you can't call Drop::drop
manually" restriction. (Just a separate ad-hoc restriction of "you must destructure in the parameter-pattern".)
The compiler already has this functionality:
struct A {
a: String,
b: String,
c: String,
}
fn x(v: A) {
y(v.a);
// `v.b` and `v.c` are dropped here but not `v.a`
}
However, this doesn't work with types that implement Drop
. So it should be possible to add a special case for drop(self)
to permit moving fields out, since drop(self)
is not going to drop self
(again).
This is a step back for Rust. Pattern matching (including on Option
) and move semantics help to avoid invalid states and random values. Broken move semantics in drop(&mut self)
nullify these benefits completely.
Right, a derive covers almost everything except manually spelling out ManuallyDrop
in the struct declaration. I did a first quick implementation and it works fine for me.
Thanks, I like that idea very much, I'll look into this.
Update: I just published drop-take.
Trait drop_take::DropTake
Take ownership of members of a type during Drop::drop
instead of only having a &mut
to them.
This trait can be derived, but doing so does not implement the trait; rather, it implements Drop
and compels you to implement DropTake
.
Your DropTake
implementation receives a tuple of all values marked #[drop_take]
, with their value extracted from the container by Take
.
If you need access to other non- #[drop_take]
values, either also #[drop_take]
them, or use Take::take
(or the functions it is a wrapper for) directly in Drop::drop
.
Example
use std::mem::ManuallyDrop;
use drop_take::DropTake;
struct Peach;
struct Banana;
struct Melon;
#[derive(DropTake)]
struct FruitBox {
#[drop_take]
peach: ManuallyDrop<Peach>,
melon: Melon,
#[drop_take]
banana: ManuallyDrop<Banana>,
}
impl DropTake for FruitBox {
type Values = (Peach, Banana);
fn drop_take((peach, banana): Self::Values) {
// use `peach` and `banana` by value
// they're dropped at the end of scope
}
}
I think this Take
trait should be named UnsafeTake
. For some types like Cell
and ManuallyDrop
, the take
method is indeed unsafe; but for Option
, it is actually safe!
Check again. Cell::take
is safe. (With another look at the function, though, I’m unsure whether it holds the invariant of my Take
trait, as you will end up dropping a value from the “consumed” container: a value of Default::default
. It’s not a safety problem, though; just a small gotcha.)
I don’t disagree; I chose Take
instead of UnsafeTake
merely because it was shorter and I had to choose something. My biggest reason for not calling it UnsafeTake
was that it isn’t an unsafe
trait. (Should it be?)
Cell::take
is safe only for Default
types. But your Take
trait is unconditional. So if we have a Take
with a safe take
method unconditional, Cell
cannot implement it.
Should UnsafeTake
be an unsafe
trait? I think yes, if all purpose of it is to use its only unsafe
method.
Cell::take
doesn’t exist for T: !Default
. It can’t. I have impl<T: Default> Take for Cell<T>
.
And you’ve not given an actual reason for the trait to be unsafe
. An unsafe trait
means that it’s unsafe to implement. Arbitrary safe code in a Take::take
implementation could not be used to create unsafety (as I understand the trait), so it shouldn’t be unsafe
to implement.
This is not necessarily true. There's always mem::swap(), which can swap a value in one location (e.g. a struct) with another one without needing a wrapper type. This does come at the cost of creating that new value though.
It's not only the added costs, but you also must be able to create such a replacement value in the first place.
And, "null-like" values for the sole purpose of replacement are exactly what I want to avoid.
Nice, at first glance it looks pretty much like what I do so far as well, except that I specify the function to be used for destructuring, instead of adding a trait.
As we discussed before, macros solve all of the problem except for the need to spell out ManuallyDrop
in the struct
declaration.
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.