5 ms·
> 3. Throwing in destructors is not a C++ problem, it's a general semantic problem around resources that aren't guaranteed to be freed up successfully. They are
by fluffything 7y ago
> 3. Throwing in destructors is not a C++ problem, it's a general semantic problem around resources that aren't guaranteed to be freed up successfully. They are a pain in any language and you can only do best-effort approaches for not leaking them.
C++ is quite good here, arguably quite better than Rust, since in C++ you can add a `noexcept(true)` clause to your destructor, and if it throws, your program terminates.
In Rust, you cannot really do that (e.g. catch_unwind won't catch exceptions thrown by sub-object destructors).
And well, C++ tries to completely ban throwing destructors by default, while Rust tries to support that as much as possible.. with the most common consequence being memory leaks..
- steveklabnik 7y agoI am not sure what you mean here. "throwing in a destructor", aka "panic during Drop" in Rust terms, runs the risk of seeing "thread panicked while panicking. aborting." In general, Rust treats panics as being something that terminates execution, not something for error handling. Where are you seeing these memory leaks in Rust code?
- fluffything 7y agoIn Rust, this destructor is perfectly fine: impl Drop for Foo { fn drop(..) { self.cleanup(); self.free_memory(); } } even if `self.cleanup()` panics. When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. If there is a `catch_unwind` somewhere, these leaks will grow over time. The same code in C++: ~foo() { this->cleanup(); this->free_memory(); } does not have this issue, because if `this->cleanup()` unwinds, that unwind makes `~foo` abort, because `~foo` is `noexcept(true)` by default. In Rust, one cannot make `Drop::drop` be "nounwind", so to emulate C++'s behavior, one needs to write: impl Drop for Foo { fn drop(...) { if let Err(_) = cath_unwind(|| { self.cleanup(); self.free_memory(); }) { abort(); } } } or use a `DropGuard` to make sure that `self.free_memory()` is called if `self.cleanup` throws, etc. Destructors being fallible are very weird things. In C++, destructors are infallible (at least by default). Rust supports fallible destructors, with all its cost (every time an object is dropped, doing this can fail, adding another "return" point from your function), yet minimal or zero value. At least, I couldn't find any examples in the Rust book / reference / rust-by-example that show in which situations unwinding from a destructor is a good thing to do.
- status_quo69 7y agoUnless I'm misunderstanding your comment, you can make panics abort rather than unwind. https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/aborting-on-panic.html https://doc.rust-lang.org/edition-guide/rust-2018/error-hand...
- imron 7y agoYou are misunderstanding the comment, because it's specifically talking about the case when panic is set to unwind and there is catch_unwind handler.
- jstrong 7y agoif `self.cleanup()` panics, the program crashes, though? How is a memory leak even a relevant concept in that case?
- andrewflnr 7y agoOnly if someone specifically catches the panic, averting the crash. That's what the catch_unwind stuff was about.
- steveklabnik 7y ago> In Rust, this destructor is perfectly fine: Sort of; it's very rare. You don't really write free_memory, unless you're doing some very specific unsafe things. > When that happens, `self.free_memory` will never be called, and memory will be leaked if `panic=unwind`, which is the default behavior. It will be leaked if panic=unwind and if you use catch_panic. catch_panic is not used very much. Partially because panic=abort is also a valid semantic, and partially because if this Drop impl is called when panic happens, regardless of panic settings, you'll get an abort if self.cleanup() panics. > Rust supports fallible destructors, This isn't really true. Fallability in Rust is spelled "Result<T, E>", and drop does not return one. You're talking about a non-recoverable error, which, it is true, Rust does not give you tools to prevent, really. That's because it's very, very rarely used, because the whole intention is to end the current thread of execution. > At least, I couldn't find any examples in the Rust book / reference / rust-by-example that show in which situations unwinding from a destructor is a good thing to do. There isn't, which is why it isn't done, which is why your point confused me :)
- gpderetta 7y agoNote that noexcept(true) in destructors has been the default for a while. It was a breaking change, but there is very little code that can handle throwing destructors that in practice jad little consequences.