5 ms·
What? It's very easy to get use-after-invalidation in Rust. Destructors called during unwinding see stuff in an invalid state. You can probably make a language
by cousin_it 9y ago
What? It's very easy to get use-after-invalidation in Rust. Destructors called during unwinding see stuff in an invalid state. You can probably make a language that prevents use-after-invalidation in safe code (e.g. mark all accessible mutable references as "dirty" during unwinding, and require unsafe code to "clean" them) but Rust isn't trying to do that AFAIK.
- shepmaster 9y agoCould you provide an example of such code? I was under the impression that certain things were disallowed because destructors aren't allowed to see the struct in an invalid state. A common case where I see people trying to do this is when you have a struct where you are trying to replace a member variable: struct Foo { thing: Vec<i32>, } impl Foo { fn something(&mut self) -> Vec<i32> { let temp = self.thing; // If we panicked between these two lines, then the struct would be in an undefined state self.thing = vec![1]; temp } } This code produces the error `cannot move out of borrowed content`. For those curious, you normally would write this as use std::mem; impl Foo { fn something(&mut self) -> Vec<i32> { mem::replace(&mut self.thing, vec![1]) } }
- deleted 9y ago[deleted]
- cousin_it 9y agoHow 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.
- shepmaster 9y agoI 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 } }