7 ms·
1. That "C equivalent" for error handling is not an equivalent at all. If one could handle the error in the same function then there is no reason to throw. The
by leni536 7y ago
1. That "C equivalent" for error handling is not an equivalent at all. If one could handle the error in the same function then there is no reason to throw. The "C equivalent" is returning an error code, which has other problems.
2. One can handle errors in initialization without exceptions and half-initialized objects: Make your constructor private and expose a static member function that returns an optional<T> (where T is the given class).
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.
- 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.
- cmckn 7y ago> If one could handle the error in the same function then there is no reason to throw. Thank you, this made absolutely no sense to me. I use exceptions for "panic" situations -- when you can't recover from an error. If you can recover...why throw an exception?
- afc 7y agoCompletely agree. I was about to write something along these lines but you did a great job. :-) In a decade using C++, I haven't encountered these problems. I never use exceptions and I do exactly what you suggested in #2: if construction can fail, have a factory method and a private constructor (I use a simple ValueOrError<T> type, rather than optional<T>, to be able to communicate information about the error, though: https://github.com/alefore/edge/blob/876c4328610262b11fda553b87de0b7faa3bf263/src/value_or_error.h#L30 https://github.com/alefore/edge/blob/876c4328610262b11fda553...). Cheers!
- thisgoodlife 7y agoI like the way how google does it with StatusOr and some status macros https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/stubs/statusor.h https://github.com/protocolbuffers/protobuf/blob/master/src/... https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/stubs/status_macros.h https://github.com/protocolbuffers/protobuf/blob/master/src/...
- hoistbypetard 7y ago> 2. One can handle errors in initialization without exceptions and half-initialized objects: Make your constructor private and expose a static member function that returns an optional<T> (where T is the given class). This comment criticizes an article written in 2012 about a project developed starting in 2007 for not using a technique that debuted in the 2017 edition of ISO C++. For projects that could force all consumers to use C++11 or later, it'd be straightforward to add your own. The idea of doing so in a cross-compiler way when you need to support the original ISO standard or C++0x in the late 2000s sounds very problematic.
- ChrisLomont 7y agoIt's not hard to write an optional<T> in C++ 2003, and likely can be done in C++ 98. If you don't like that, return a T*, null if not created correctly.
- hoistbypetard 7y agoI didn't mean to suggest the language spec would have made it hard so much as that I'd have really not wanted to approach it in a way that was going to work with VC6/2002/2005/gcc 2.95/sunpro/ibm/hp compilers of the era. (I think it was 2009 before I got to push VC6 off the raft.) ISO support in C++ compilers was really bad for a long time. Our typical pattern was to return T*. But that fits well with the "I should have just written it in C" argument IMO.
- deleted 7y ago[deleted]
- deleted 7y ago[deleted]