I'm using gfx-hal
, which requires me to create resources which need to be explicitly destroyed using functions specific to their type. I'd like to store instances of these types in structs, and I'd also like to tie cleaning them up to the lifetime of the owning struct, instead of managing their lifetimes manually and potentially having objects on the GPU/in the driver live forever.
However, all the functions in the destroy
family of functions take the type directly, rather than a reference, so when I try to pass them from my structs, I get errors like the following:
error[E0509]: cannot move out of type `S`, which implements the `Drop` trait
--> src/lib.rs:9:18
|
9 | destroyT(self.member)
| ^^^^^^^^^^^ cannot move out of here
It seems like there should be some way around this issue, as I'm currently in the Drop::drop
function itself, so self
is already "consumed." How do I get the instances of these types out of self
as T
, and not &T
?
struct T;
struct S {
member: T,
}
impl Drop for S {
fn drop(&mut self) {
destroyT(self.member)
}
}
// elsewhere, in a library
fn destroyT(t: T) {
//...
}
The safest, easiest way to do this is to use an Option
:
struct T;
impl Drop for T {
fn drop(&mut self) {
println!("dropping T");
}
}
struct S {
member: Option<T>,
}
impl Drop for S {
fn drop(&mut self) {
if let Some(t) = self.member.take() {
destroy_t(t);
}
}
}
fn destroy_t(_t: T) {
println!("destroy T");
}
fn main() {
let _x = S { member: Some(T) };
}
You could choose to use unsafe code with ManuallyDrop
and swap out the current value for an uninitialized one1:
use std::mem::{self, ManuallyDrop};
struct T;
impl Drop for T {
fn drop(&mut self) {
println!("dropping T");
}
}
struct S {
member: ManuallyDrop<T>,
}
impl Drop for S {
fn drop(&mut self) {
unsafe {
let valid_t = mem::replace(&mut *self.member, mem::uninitialized());
destroy_t(valid_t);
// do *not* call ManuallyDrop::drop
};
}
}
fn destroy_t(_t: T) {
println!("destroy T");
}
fn main() {
let _x = S {
member: ManuallyDrop::new(T),
};
}
1 Using mem::uninitialized
is extremely dangerous and hard to get right, especially in generic contexts. Using the nightly MaybeUninit
, this might look like
#![feature(maybe_uninit)]
use std::mem::{self, ManuallyDrop, MaybeUninit};
struct T;
impl Drop for T {
fn drop(&mut self) {
println!("dropping T");
}
}
struct S {
member: ManuallyDrop<MaybeUninit<T>>,
}
impl Drop for S {
fn drop(&mut self) {
let invalid_t = MaybeUninit::uninitialized();
let valid_t = mem::replace(&mut *self.member, invalid_t);
let valid_t = unsafe { valid_t.into_inner() };
destroy_t(valid_t);
// do *not* call ManuallyDrop::drop
}
}
fn destroy_t(_t: T) {
println!("destroy T");
}
fn main() {
let _x = S {
member: ManuallyDrop::new(MaybeUninit::new(T)),
};
}
See also:
- How to move one field out of a struct that implements Drop trait?