I understand that the author of LibType should make cleanup in LibType::drop and prevent the panicking and so on, but he/she is already make a mistake and I cannot fix it, because have no access to the code (let imagine it is distributed as propriatory crate).
As far as I know, this code still causes calling abort function because of double panicking:
fn test() {
let t = MyType::new();
panic!("Test!");
}
C++ doesnt allow destructors to panic during unwinding. Because of this the native unwinder may not support this either. This means that rust cannot support it across different OSes, as it uses the native unwinder.
It seems I cannot understand. Destructor in C++ should not throw exception by the same reason that destructor in Rust should not panic in drop - to avoid double throwing (which is causes std::terminate in C++ case).
But here is absolutely the same of the situation described in post which is written in C++ and everything fine with unwinding: cpp.sh/8lm7
That's not sounds good and seems like a thing which is should be fixed in language Such behaviour makes improssible to use RAII properly and be sure, that all resources in program will be freed on program closing.
What leads to an instant abort is if, during a panic!, when unwinding the stack and thus running the appropriate "destructors", one of these Drop::drop leads to a second panic!. In that case Rust does indeed abort.
fn main ()
{
use ::std::panic;
let _ = panic::catch_unwind(|| {
let _struct = Struct::new();
panic!("First panic!");
// panic leads to _struct.drop(),
// which panics on its own, leading to an abort
});
// this is never reached
println!("Program keeps going");
}
If you intend to support a code pattern as the one shown above, i.e., to properly cleanup the Foo even while panic!-king, while not causing the process to abort so as to be catchable and reach further code, you will need to use closures / CPS so as to be able to "catch a caller's panic!", hold it, cleanupFoo with a second catch_unwind, and then resume the initial panic! so as to allow the caller to detect it and catch it:
fn main ()
{
use ::std::panic;
let _ = panic::catch_unwind(|| {
Struct::with_new(|_struct: &mut Struct| {
// use _struct here
panic!("First panic!");
}); // it is dropped here
});
println!("Program keeps going");
}
notice how instead of
let mut _struct = Struct::new();
// use _struct here
drop(_struct); // or end of lexical scope
we are doing
Struct::with_new(|_struct| {
// use _struct here
});
This pattern is what allows to have guaranteed "cleanup" code (where Drop::drop is unreliable due to memory leaks
@Yandros Thank you, it looks like a solution... But this solution looks ugly! And it will be worse in real program which operates by many objects and all of them must use this pattern to be safe.
You also should keep in mind that even now the objects are written correctly, it could not be true in future so you should use such pattern on EVERY OBJECT and your code will become absolutely not readable.
It could more and more convinient dust wrap all code you not own in Drop::drop to catch_unwind and just log such things, just like catching all exceptions in C++ destructor, logging them and not crashing the software by one wrong-written object.
Normal Rust code shouldn't panic. If some Drop impl is panic!()-ing that means that the world is in an indeterminate state, so there's not much the language runtime can do safely, other than crash loudly so a developer can come along and fix things. I feel like using this pattern for every object could lead to bugs or unpredictable behaviour...
I'd say the better solution is to review code and make sure it doesn't panic. Alternatively, if you are doing an operation in a Drop impl which may panic!(), you can use if !std::thread::panicking() to only execute that code when not already panicking.
If the world is in an indeterminate state, you should always use std::process::abort(), instead of panicking. Panicking should only be used when there theoretically is a sensible way to deal with the problem.
For example a browser running different tabs in different threads could keep the other tabs running when one tries to perform out of bounds indexing. Or a web server (for example hyper) could use a single thread for every request and return a 500 Internal server error when panicking and just keep handling new requests.
Where did you see a code which works as it should? If you want to write reliable software, you should write code with understanding, that bugs exist. And bad programmers too
I know about this feature, but someone who has written LibType::cleanup don't. And everything worked fine until one day, when the panic! branch in cleanup has occured.
The problem is in the fact, that I cannot avoid this situation. If something goes wrong in some thread I would like kill only the thread, not the whole process.
It is not right in programmer's point of view, but we're making software for users. Yes, after panic! we potentially have some indeterimnate state, but sometimes this state is not fair, and to handle such situation in rust we have, to put in mildly, not the best with_new solution.