Private drop, or "Rust could be better at RAII with a rather small change"


I often manipulate object that have some release logic.
To illustrate the flaw in the current situation, I’ll take the example of BufWriter.

Of course, nobody would ever want to write in a BufWriter without flushing the result in the end.
Quite naturally, BufWriter calls flush on Drop (See BufferedWriter#Drop).

There are two problems to use Drop to enforce release logic.

a) What if release can return an error. In the case of the BufWriter, the call to flush() may return an io Error that will be ignored by the BufWriter’s drop, and give us a false sense of safety : Any code that relies on Drop to flush its BufWriter is basically faulty

b) The call to Drop is implicit. Putting complex logic in it, especially if it has performance impact, panics, or side-effect is a bad idea. Unfortunately release logic may be complex.

A simple solution could be to somehow able to declare the Drop implementation as “private”. The effect would be that the object cannot be dropped from the scope outside of one of its method.

private impl Drop for BufWriter {}

After this addition, the compiler will not let any BufWriter to “leave the scope”.
To get his code to compile, the user would need to call a release function.

The name is up to the library writer, and there could be more than one for complex cases.
The idea is that it would use move semantics for self. For instance, in the case of the BufWriter,

fn close(self) -> io::Result<()> {
   if self.inner.is_some() && !self.panicked {
   else {

would make sense.

What do you think?


Here’s some discussion of the similar feature:

The effect would be that the object cannot be dropped from the scope outside of one of its method.

Note that this won’t work as is, because panics call drops. So, BufWriter still need to have logic for “looks like something crashed badly, let’s try to optimistically flush buffers”.

I think a reasonable practical solution would be to add debug_assert(self.flushed || ::std::thread::panicking(), "Please, call BufWriter::flush explicitly"), coupled with a lint.


The trouble is how would this work in other contexts where drop is called implicitly? Say storing a new value into something that has Drop, or even into a slice/Vec of such things. It might get slightly unwieldy/noisy in those cases.

I like @matklad’s practical compromise. As a design pattern, offer up fallible alternatives that users can call explicitly.


Since Drop may still be necessary for emergency cleanup, why not have an ExplicitDrop (or some other name) marker trait that requires the value to be consumed before it leaves the scope? There would have to be some kind of exception to the rule somewhere, though, otherwise it turns into some kind of hot potato. Maybe it can only be destructured? Maybe it’s ok to drop within one of its methods?


@matklad Thanks for pointing out panic!. That’s an excellent point. I’m not fond of the dynamic check, well, as it introduce a dynamic panick on Drop, but I agree it would be better than the current situation.

I actually use this exact trick in tantivy’s RamDirectory.

@ogeon Yes, you are correct. A marker would probably a smarter way to do that.