"Forget" a value but run field destructors as usual

I have a type that implements writing data in a particular format to a contained impl std::io::Write:

// bounds on struct required due to Drop, see below
pub struct MyWriter<W: Write + Seek> { inner: W }

impl<W: Write + Seek> MyWriter<W> {
    pub fn write_foo(&mut self, foo: Foo) -> Result<(), WriterError> {
        // write some bytes to `self.inner` based on `foo`
    }
}

At the end of a MyWriter's lifetime, it needs to "fix up" the data it's written by seeking backward and overwriting a previous field; let's say this is implemented by a method fixup:

impl<W: Write + Seek> MyWriter<W> {
    fn fixup(&mut self) -> Result<(), WriterError> {
        // ...
    }
}

One approach to making sure this fixup happens in (almost) all circumstances is to put it in a Drop impl, but that precludes doing any real handling of errors from fixup: either we panic in drop on an error, which will likely cause aborts during unwinding, or we ignore any errors, which seems bad. So I'd like to provide a way to perform the fixup explicitly that doesn't hide the error, like this:

impl<W: Write + Seek> MyWriter<W> {
    pub fn finish(self) -> Result<(), WriterError> {
        self.fixup()?;
        // TODO what now?
    }
}

The idea is that callers can use finish to get access to any errors that occur during fixup, but if they forget to do this or don't want to then the Drop impl will still take care of it (by panicking or ignoring any error).

So here's my question: how should I complete finish? It needs to skip the Drop impl for MyWriter (because we should only fixup once), while still running the destructors for the object's fields—in particular inner, which is probably a File or something. I thought I could use std::mem::forget for this, but apparently that won't run the fields' destructors.

You could use a boolean or option in the struct and have the destructor do nothing if it's false/None. For some types, it can make sense to replace it with a dummy value, e.g. an empty vector. It's also possible to do with unsafe.

2 Likes

This is the general problem of destructuring (as in “destructuring assignment”) a value with Drop. There’s no good solutions I believe. The one I currently use is marking the type with repr(transparent) and transmuting: https://github.com/rust-analyzer/rust-analyzer/blob/11f3231a75f74e87074bd6e4c9d61c635adffce1/crates/stdx/src/lib.rs#L142

1 Like

Good to know I'm not the only one to run into this! My actual type has multiple fields so I'll most likely use an explicit "drop flag" as @alice suggested.

Safe option is to use inner: Option<W> and .take() it in the finish().

A more efficient/less safe way is:

std::ptr::drop_in_place(&mut self.inner); // drops the field
std::mem::forget(self); // avoids dropping the field again
1 Like

I would probably prefer putting it in a ManuallyDrop before the drop_in_place rather than mem::forget here.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.