Panic if a method wasn't called before the struct was dropped

How can I panic if I a method wasn't called before the struct was dropped? E.g. I want this code to panic:

struct Foo;

impl Foo {
    /// Call this before the struct is dropped.
    fn finish(self) {}
}

fn main() {
    let foo = Foo;
    // Panic!
}

I thought of writing something like this:

struct Foo {
    finish_was_called: bool,
}

impl Foo {
    fn finish(mut self) {
        self.finish_was_called = true;
    }
}

impl Drop for Foo {
    fn drop(&mut self) {
        if !self.finish_was_called {
            panic!("you must call finish");
        }
    }
}

but it doesn't feel like the best solution. I think I've seen some examples of this being done but I can't remember where. I also thought of using #[must_use] but I don't think that would work for this use case since other methods may be called.

I can't think of a better way offhand. If the information isn't in the struct it'd have to be in some locky unique-struct-identifying global resource or such.

But FYI, #[must_use] just makes sure you somehow acknowledge the returned value, you don't really even have to use it:

use std::io::Write;

fn main() {
    // Warns
    std::io::stdout().write_all(b"hello ");
    
    // Does not warn
    let _ = std::io::stdout().write_all(b"world\n");
}

(It's also a compile time lint and not a run time panic.)

Be aware that code must not rely on destructors being called for safety purposes. You might want to read Pre-Pooping Your Pants With Rust (no, really).

1 Like

You may also want to check drop_bomb, either to use it directly or to look through the source.

1 Like

Another way might be to use a wrapper type :

struct RealFoo;

struct Foo(RealFoo);

impl Drop for Foo {
    fn drop(&mut self) {
        panic!("you must call finish")
    }
}

impl Foo {
    fn finish(self) -> RealFoo {
        // safe because `Foo` and `RealFoo` have the same layout.
        unsafe { std::mem::transmute(self) }
    }
}

This comment is incorrect. Transmuting between two repr(Rust) types is not safe because two repr(Rust) types are not guaranteed to have the same layout. Quoting the Nomicon:

So how do you know if the layouts are the same? For repr(C) types and
repr(transparent) types, layout is precisely defined. But for your
run-of-the-mill repr(Rust), it is not. Even different instances of the same
generic type can have wildly different layout. Vec<i32> and Vec<u32>
might have their fields in the same order, or they might not. The details of
what exactly is and is not guaranteed for data layout are still being worked
out over at the UCG WG.

1 Like

Yeah, I'm not relying on finish() being called for safety purposes. I just want to make sure it's called so that state is propagated.

1 Like

I ended up figuring out a better solution. I refactored this:

impl Foo {
    fn do_stuff(&mut self) {}
    fn finish(self) -> Result<SomeData> {}
}

Into this:

impl Foo {
    fn do_stuff(mut self) -> Result<SomeData> {}
}

Which statically guarantees that Result<SomeData> is received by the caller, which then triggers a #[must_use] on the Result.

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.