Hacker News new | ask | show | jobs
by cousin_it 3329 days ago
How about this?

    struct Foo { bar: Bar }
    struct Bar { message: &'static str }

    fn change_foo(foo: &mut Foo) {
        change_bar(&mut foo.bar);
    }

    fn change_bar(bar: &mut Bar) {
        bar.message = "Invalid";
        if true {
            panic!();
        }
        bar.message = "Valid";
    }

    impl Drop for Foo {
        fn drop(&mut self) {
            println!("{}", self.bar.message);
        }
    }

    fn main() {
        let mut foo = Foo { bar: Bar { message: "" } };
        change_foo(&mut foo);
    }
The destructor of a struct sees a broken invariant of a nested struct.
1 comments

I see, I think you are using a different definition of "invalid" than I and the grandparent are. Rust will not allow you to access memory that is invalid, but your own invariants can certainly be broken.

For what it's worth, the solution I've seen for this type of case is another struct that is used to restore to an acceptable state:

    fn change_bar(bar: &mut Bar) {
        let mut restore = Restore(bar);
        restore.message = "Invalid";
        if true {
            panic!();
        }
        restore.message = "Valid";
        // Don't rollback on success
        std::mem::forget(restore); 
    }
    
    struct Restore<'a>(&'a mut Bar);
    
    // Put back to an acceptable state
    impl<'a> Drop for Restore<'a> {
        fn drop(&mut self) {
            self.0.message = "Restored to some state";
        }
    }

    // Sugar so we don't have to know about the wrapper
    impl<'a> std::ops::Deref for Restore<'a> {
        type Target = Bar;
        fn deref(&self) -> &Bar { self.0 }
    }
    
    // Sugar so we don't have to know about the wrapper
    impl<'a> std::ops::DerefMut for Restore<'a> {
        fn deref_mut(&mut self) -> &mut Bar { self.0 }
    }