What happens after one of the elements of a Vec panics when it drops?
I don’t think it is defined anywhere, but my mental model is that the execution of Vec::drop function would be interrupted, so any elements that have not already been dropped will not be dropped.
I think if you write a Vec in Rust without doing something strange ( such as catching the panic ), this is what would happen.
However it seems that std::Vec DOES “catch the panic” (somehow - I don’t know how) and does drop more elements.
Playground example
Perhaps this is all a little academic, as also in my mental model is drop functions should/must not panic, as it sabotages panic recovery, but I just want to understand the situation.
If you panic twice it aborts (>= 2). I'm sure this is not guaranteed; I forget if it's an implementation detail of Vec or slice drops (on mobile).
See also
https://github.com/rust-lang/rfcs/pull/3288
Vec::drop simply drops the slice, so it's the latter. And slice, being a built-in type, can have some non-trivial behavior, I guess?
So ptr::drop_in_placeis an undocumented private function with unknown semantics?
Edit: Sorry, it is documented.
But it doesn’t say anything about panics. Oh, and anyway that is just dropping one thing.
the behavior of slice destruction is actual pretty trivial : they behave exacly like arrays, which themself behave exactly like a struct with N fields of the same type.
when dropping a struct (or a slice, or an array), each field is dropped in order from first to last. if one of the destructors of a field panic, it simply continues to drop the subsequent fields afterwards, but is now in unwinding mode, which means that once the drop is finished it wil start unwinding the stack. ofc if panic=abort it immediately aborts., and panicking while in unwnding mode also aborts
1 Like
Thanks. Is that documented somewhere?
Well it is sort-of documented here but it doesn’t say anything about panics:
a simple example is
struct Panicky;
impl Drop for Panicky {
fn drop(&mut self) {
println!("this is very stressful");
panic!()
}
}
fn main() {
{
let x : [Panicky;5] = [const { Panicky };5];
}
}
will lead to
// stdout
this is very stressful
this is very stressful
// stderr
thread 'main' (53) panicked at src/main.rs:6:9:
explicit panic
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' (53) panicked at src/main.rs:6:9:
explicit panic
// .....
panic in a destructor during cleanup
thread caused non-unwinding panic. aborting.
same if you do
{
let x : (Panicky, u32, Panicky) = (Panicky, 0, Panicky);
}
or
{
let x : Vec<Panicky> = [const { Panicky };5].into();
}
or a custom struct
i think so ? i feel like i read it in some doc but i don't remember where.
edit :well seems not in official doc, mb
Vec first drops all items in ascending order, and then de-allocates the vector itself
If drop panics for a single item, it continues to drop all other items and the vector itself.
If drop panics for a second item, the program aborts since it's a double-panic.
This behaviour is currently undocumented
playground
This behaviour doesn't need catch_unwind. It can be achieved by having a guard struct on the stack while dropping the slice, which continues dropping in its drop implementation. But the actual implementation of ptr::drop_in_place is compiler magic, so you can't easily look at it.
2 Likes
Ah thanks, that is the confirmation I wanted.
I think it should be explicitly documented as undefined, that is, panic in a drop function may terminate the program immediately, may terminate the destruction of the current array/struct/whatever, followed by unwinding, or may continue with the destruction of further elements, followed by unwinding.
My reasoning is it isn’t really useful for this to be defined, since it doesn’t make sense for a drop function to panic, so implementations should be able to do whatever is “cheapest”.
but it atually is useful. it's actually very important.
unsafe code relies on the current implementation of unwinding algorithm to allow for sound recovery after panics.
There are efforts to add a new panic mode to rust where panics in drops are always fatal, even if it's not a double-panic. Such a mode makes a lot of sense to me, and would likely be what I'd choose for my own applications if it were available.
Though they should fix catch_unwind first, so it shields against aborts from both this and double-panics. looks like catch_unwind actually works for double-panics. Apparently that changed in Rust 1.71, but didn't show up in the announced changes.
But I expect this to be added as a new panic mode similar to the current selection between unwind, abort and immediate-abort.
1 Like
Do you mean recovery after normal panics, or after panics in drop functions?
I would agree with that. I mean I think that is how it should have been defined to begin with.
that's not a useful distinction. i mean unwinding panics.
a panic can either abort,in which case the program is immediately terminated with no cleanup, or unwind,in which case the program will unwind the stack until reaching main or some type of unwind catching boundary.
if an unwinding panic is triggered, the program must ensure it stays in a coherent state so as not to trigger UB while unwinding or after the unwindin has been recovered from.
if what gets dropped or not where to become undefined, then it would become impossible to make sure of that.
aborting immediately if you're in a destructor is fully safe of course, but it's also something many users don't want. they want to recover from as many panics as possible
I don’t think it is useful to be able to recover from a panic in a drop function ( destructor ), such panics are I think bizarre and “improper” in some sense. I cannot think of the correct word.
Yes, you can can abort when a panic happens in drop. But if you don't, you need to make sure that the surviving state is consistent enough to continue. And that often requires running another drop function when the first drop function panics. So the language has to make some guarantees about how unwinding from drop behaves.
Sure, and in Safe Rust that is easy. If you are doing tricks with unsafe Rust you have to be more careful. But other than staying memory safe, I don’t think more has to be defined, in particular leaks are to be expected after a panic in a drop function ( but not after a “normal” panic ).
There's actually multiple subcases within aborts as well. Either the program calls the global panic handler first and then terminates, or it outputs a simple error to stderr before terminating (without ever calling the panic handler), and for all I know there might be a way to terminate the process without even printing to stderr.