50 lines
2.8 KiB
Markdown
50 lines
2.8 KiB
Markdown
% Unwinding
|
|
|
|
Rust has a *tiered* error-handling scheme:
|
|
|
|
* If something might reasonably be absent, Option is used.
|
|
* If something goes wrong and can reasonably be handled, Result is used.
|
|
* If something goes wrong and cannot reasonably be handled, the thread panics.
|
|
* If something catastrophic happens, the program aborts.
|
|
|
|
Option and Result are overwhelmingly preferred in most situations, especially
|
|
since they can be promoted into a panic or abort at the API user's discretion.
|
|
Panics cause the thread to halt normal execution and unwind its stack, calling
|
|
destructors as if every function instantly returned.
|
|
|
|
As of 1.0, Rust is of two minds when it comes to panics. In the long-long-ago,
|
|
Rust was much more like Erlang. Like Erlang, Rust had lightweight tasks,
|
|
and tasks were intended to kill themselves with a panic when they reached an
|
|
untenable state. Unlike an exception in Java or C++, a panic could not be
|
|
caught at any time. Panics could only be caught by the owner of the task, at which
|
|
point they had to be handled or *that* task would itself panic.
|
|
|
|
Unwinding was important to this story because if a task's
|
|
destructors weren't called, it would cause memory and other system resources to
|
|
leak. Since tasks were expected to die during normal execution, this would make
|
|
Rust very poor for long-running systems!
|
|
|
|
As the Rust we know today came to be, this style of programming grew out of
|
|
fashion in the push for less-and-less abstraction. Light-weight tasks were
|
|
killed in the name of heavy-weight OS threads. Still, on stable Rust as of 1.0
|
|
panics can only be caught by the parent thread. This means catching a panic
|
|
requires spinning up an entire OS thread! This unfortunately stands in conflict
|
|
to Rust's philosophy of zero-cost abstractions.
|
|
|
|
There is an *unstable* API called `catch_panic` that enables catching a panic
|
|
without spawning a thread. Still, we would encourage you to only do this
|
|
sparingly. In particular, Rust's current unwinding implementation is heavily
|
|
optimized for the "doesn't unwind" case. If a program doesn't unwind, there
|
|
should be no runtime cost for the program being *ready* to unwind. As a
|
|
consequence, *actually* unwinding will be more expensive than in e.g. Java.
|
|
Don't build your programs to unwind under normal circumstances. Ideally, you
|
|
should only panic for programming errors or *extreme* problems.
|
|
|
|
Rust's unwinding strategy is not specified to be fundamentally compatible
|
|
with any other language's unwinding. As such, unwinding into Rust from another
|
|
language, or unwinding into another language from Rust is Undefined Behaviour.
|
|
You must *absolutely* catch any panics at the FFI boundary! What you do at that
|
|
point is up to you, but *something* must be done. If you fail to do this,
|
|
at best, your application will crash and burn. At worst, your application *won't*
|
|
crash and burn, and will proceed with completely clobbered state.
|