The Rust programming language provides a few ways to terminate a program when it reaches an unrecoverable state by calling
the macro std::panic!
- a reference to kernel panic that I have found quite amusing.
It comes in handy when an assert needs to used within code, such as for unit tests, and it is eventually called by the
method unwrap
of the Option
and Result
enums.
From my experience as a C/C++ engineer (I hope C and C++ enthusiasts, as well as the almighty coding standard Gods, will
forgive me for this blasphemy of placing a slash between the two languages), panic!
was initially a synonym of
abort
in C and C++, but with a few more features, such as stack unwinding. The goal of this post is to shed some light
on a few of the differences between panic!
and abort
that I have personally encountered.
Let us start with a simple program that immediately ‘panics’ when it is run:
1
2
3
4
fn main() {
panic!("Panic in the main thread!");
println!("Hello, world!");
}
The program is terminated and the output reveals where the panic was triggered. As a bonus, the application can be configured via an environment variable to show its backtrace (stack unwinding).
$ cargo run
Hello, world!
thread 'main' panicked at 'Panic in the main thread!', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
It becomes even more intriguing when the exit code of the process is checked right after termination:
$ echo $?
101
Rust sets the exit code to 101
explicitly when a process panics by calling the exit
function, while abort
signals
the kernel to kill the process (a detailed explanation of how abort
works on Unix systems can be found in an earlier
post). In practice, this means that no core dumps are
generated in the default configuration.
Now, let us take a look at what happens when panic!
is called from a sub-thread:
1
2
3
4
5
6
7
8
9
10
11
12
13
use std::thread;
fn main() {
let handle = thread::spawn( || {
println!("Thread started!");
panic!("Panic in a thread!");
});
handle.join();
println!("Hello, world!");
}
Output:
$ cargo run
Thread started!
thread '<unnamed>' panicked at 'Panic in a thread!', src/main.rs:7:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Hello, world!
$ echo $?
0
The output clearly states that the thread has panicked but the main thread continues running, even after calling join
!
It can thus be concluded that panic
does not exit the entire process, but rather only the current thread; this is
completely different from C’s abort
!
My continued interest in the Rust language grows precisely due to features such as this, where the language provides elegant methods for terminating a process in the case where a background thread crashes.
If we were to force an ultimatum on the result of join
, the shortest way is to unwrap
the return value:
1
2
3
...
handle.join().unwrap();
...
The result contains an error and unwrapping leads to panic in the main thread:
$ cargo run
Thread started!
thread '<unnamed>' panicked at 'Panic in a thread!', src/main.rs:7:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Any', src/main.rs:10:19
Another way to manipulate the output of join
is to check the result and decide what to do during runtime; the
following example uses match
:
1
2
3
4
match handle.join() {
Ok(_) => println!("Joined!"),
Err(_) => println!("Join failed"),
};
Note that this example only prints the error and the program still exits with 0
.
But wait, there’s more!
For those who are not big fans of change, Rust even provides the possibility to configure panic!
to call abort
; this
can be done via Cargo.toml in the project:
1
2
3
4
5
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
The result is the same as calling abort
in C: the application is terminated with SIGABRT
and if the system is
configured, a core dump is generated:
$ cargo run
Thread started!
thread '<unnamed>' panicked at 'Panic in a thread!', src/main.rs:7:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[1] 67943 abort cargo run
134
Rust’s flexibility truly does not cease to amaze and I will diligently continue to provide such examples which I believe other enthusiasts should be aware of and use.
Special thanks to Rina Volovich for editing.