Weekly-meetings/2014-10-30 (error conventions; cargo; namespaced enums; trait-based error handling; macro unification; coercions; dynamic linking, byte literals, failing dtors)


#1

This was not actually a ‘weekly meeting’ since we already had one of those this week, but it was the same format, to get some more RFC’s approved.


#2

acrichto: Most sensible thing to me is aborting if you panic in a dtor but detecting is expensive nmatsakis: Why would you call that sensible? Is there any language that does this?

C++ for example…

Edit: To clarify. I might have misunderstood what this is about. C++ calls std::terminate() if an exception is thrown while unwinding from a previous exception, e.g. if a destructor throws an exception during unwinding. But not if a destructor throws during “normal operation”.


#3

So, since that meeting I’ve given this a lot of thought and have definitely weakened from the rather strong position I took at the time. Nonetheless, to clarify: this is not about that case, it’s about the case where you call a destructor normally and then initiate unwinding from within the destructor.

TL;DR I think we should do what C++ does or something very close to it, whatever it is.

In general I have the impression that there are sort of two reasonable things to do when a destructor panics (whether or not you are already unwinding, imo) and both of them have their place:

  1. Abort eagerly. One way to do this in a (relatively) cost-free way would be to initiate unwinding but to have the landing pad in any destructor abort. This will cause it to abort…eventually.
  2. Best-effort recovery. How I see it, there is kind of a tree of destructors to run, with a spine rooted in the stack frames / local variables but with children extending down into each field and so forth. If a destructor panics, it seems reasonable to skip that node but keep unwinding as best you can.

The problem with option 2 is that

(a) depending on how hard we work at it, it may have performance costs; (b) no matter what, something is almost certainly going to leak, maybe a lot (at minimum, the dtor that panics failed to complete, and if that is e.g., a smart pointer, then its referent may not be dropped either); © particularly in the recursive case, the C++ machinery is not setup to accommodate it.

I can easily imagine applications for which leaking is preferable to aborting, and I can also easily imagine applications for which slight performance costs may be acceptable.

But to be honest I think at the end of the day, as I said in the TL;DR, this is not an area where it is worth innovating. We should try to match the behavior of C++ with reasonable fidelity – if nothing else it will make integrating with C++ codebases easier (though I’ve never worked on one that actually used C++ exceptions…) and allow us to reuse more machinery and so forth. I need to do some research (and haven’t) on the precise behavior of C++ in the various cases.

Well, that’s roughly the current state of my thoughts.


#4

Swallowing panics at fn drop boundaries (and still running the drop glue, and of course re-raising the panics quickly) would run well, preventing panic-related leaks from safe code.

By the way, I would prefer that we abolish running destructors on unwinding, as it is a relatively bad way of managing resources.


#5

People tend to think of destructors as synonymous with “freeing resources”, and therefore of failing to run them with leaking, but is it really this simple? RAII has many applications. We could have scope guards like D, commit/rollback of database transactions, temporary files (okay, these are a resource), existing things like Mutex and RefCell, and I’m probably not being creative enough at the moment. I suspect that failing to run destructors could sometimes have a serious impact on program correctness, and it doesn’t seem far-fetched that in some cases it could lead to Very Bad Things such as data loss. (I’m not sure if there’s any circumstance where memory safety could be implicated.)

(I’m not suggesting any particular conclusion, but I haven’t seen this sort of thing being taken into consideration.)


#6

The OOM killer can give you a SIGKILL at any time – you do need to handle exiting-without-destructors properly. I’ll imagine that any real database rolls a transaction back if its controlling connection disconnects. You don’t want to touch RefCell-s on failure, and you can handle mutexes with robust futexes.