x-i18n: generated_at: “2026-03-01T14:01:46Z” model: gemini-3-flash-preview provider: google-gemini-cli source_hash: edce46b44eee961106ca49be8b1ce306e400c767304e9005b8763c141a444d1d source_path: ch09-01-unrecoverable-errors-with-panic.md workflow: 16
使用 panic! 处理不可恢复的错误 (Unrecoverable Errors with panic!)
Unrecoverable Errors with panic!
有时代码中会发生一些糟糕的事情,而你对此无能为力。在这种情况下,Rust 提供了 panic! 宏。在实践中,有两种引发恐慌的方式:采取导致代码恐慌的行动(例如访问超出数组末尾的位置)或显式调用 panic! 宏。在这两种情况下,我们都会在程序中引发恐慌。默认情况下,这些恐慌将打印一条失败消息、展开(unwind)栈、清理栈并退出。通过环境变量,你还可以让 Rust 在发生恐慌时显示调用栈,以便更容易追踪恐慌的来源。
Sometimes bad things happen in your code, and there’s nothing you can do about
it. In these cases, Rust has the panic! macro. There are two ways to cause a
panic in practice: by taking an action that causes our code to panic (such as
accessing an array past the end) or by explicitly calling the panic! macro.
In both cases, we cause a panic in our program. By default, these panics will
print a failure message, unwind, clean up the stack, and quit. Via an
environment variable, you can also have Rust display the call stack when a
panic occurs to make it easier to track down the source of the panic.
针对恐慌展开栈或中止 (Unwinding the Stack or Aborting in Response to a Panic)
Unwinding the Stack or Aborting in Response to a Panic
默认情况下,当恐慌发生时,程序开始“展开 (unwinding)”,这意味着 Rust 会回溯栈并清理它遇到的每个函数中的数据。然而,这种回溯和清理工作量很大。因此,Rust 允许你选择另一种方案:立即“中止 (aborting)”,即不清理直接结束程序。
By default, when a panic occurs, the program starts unwinding, which means Rust walks back up the stack and cleans up the data from each function it encounters. However, walking back and cleaning up is a lot of work. Rust therefore allows you to choose the alternative of immediately aborting, which ends the program without cleaning up.
程序当时使用的内存将需要由操作系统来清理。如果在你的项目中,你需要让生成的二进制文件尽可能小,你可以通过在 Cargo.toml 文件的适当
[profile]部分添加panic = 'abort',将发生恐慌时的行为从展开切换为中止。例如,如果你想在发布模式下发生恐慌时中止,请添加:Memory that the program was using will then need to be cleaned up by the operating system. If in your project you need to make the resultant binary as small as possible, you can switch from unwinding to aborting upon a panic by adding
panic = 'abort'to the appropriate[profile]sections in your Cargo.toml file. For example, if you want to abort on panic in release mode, add this:[profile.release] panic = 'abort'
让我们在一个简单的程序中尝试调用 panic!:
Let’s try calling panic! in a simple program:
文件名: src/main.rs
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch09-error-handling/no-listing-01-panic/src/main.rs}}
}
当你运行程序时,你会看到类似这样的内容:
When you run the program, you’ll see something like this:
{{#include ../listings/ch09-error-handling/no-listing-01-panic/output.txt}}
对 panic! 的调用导致了最后两行中包含的错误消息。第一行显示了我们的恐慌消息以及源代码中发生恐慌的位置:src/main.rs:2:5 表明它是 src/main.rs 文件的第二行第五个字符。
The call to panic! causes the error message contained in the last two lines.
The first line shows our panic message and the place in our source code where
the panic occurred: src/main.rs:2:5 indicates that it’s the second line,
fifth character of our src/main.rs file.
在这种情况下,指示的行是我们代码的一部分,如果我们转到该行,我们就会看到 panic! 宏调用。在其他情况下,panic! 调用可能位于我们的代码所调用的代码中,错误消息报告的文件名和行号将是调用 panic! 宏的他人的代码,而不是最终导致 panic! 调用的我们代码的那一行。
In this case, the line indicated is part of our code, and if we go to that
line, we see the panic! macro call. In other cases, the panic! call might
be in code that our code calls, and the filename and line number reported by
the error message will be someone else’s code where the panic! macro is
called, not the line of our code that eventually led to the panic! call.
我们可以使用 panic! 调用源自的函数的“回溯 (backtrace)”来找出导致问题的代码部分。为了理解如何使用 panic! 回溯,让我们看另一个例子,看看当 panic! 调用由于我们代码中的 bug 而源自库(而不是由我们的代码直接调用宏)时是什么样子的。示例 9-1 中的代码尝试访问向量中超出有效索引范围的索引。
We can use the backtrace of the functions the panic! call came from to figure
out the part of our code that is causing the problem. To understand how to use
a panic! backtrace, let’s look at another example and see what it’s like when
a panic! call comes from a library because of a bug in our code instead of
from our code calling the macro directly. Listing 9-1 has some code that
attempts to access an index in a vector beyond the range of valid indexes.
#![allow(unused)]
fn main() {
{{#rustdoc_include ../listings/ch09-error-handling/listing-09-01/src/main.rs}}
}
在这里,我们尝试访问向量的第 100 个元素(位于索引 99 处,因为索引从零开始),但该向量只有三个元素。在这种情况下,Rust 会引发恐慌。使用 [] 应该返回一个元素,但如果你传递一个无效的索引,Rust 在这里无法返回任何正确的元素。
Here, we’re attempting to access the 100th element of our vector (which is at
index 99 because indexing starts at zero), but the vector has only three
elements. In this situation, Rust will panic. Using [] is supposed to return
an element, but if you pass an invalid index, there’s no element that Rust
could return here that would be correct.
在 C 语言中,尝试读取超出数据结构末尾的位置是未定义行为。你可能会得到该内存位置上的任何内容(尽管该内存并不属于该结构体),该位置对应于该数据结构中的那个元素。这被称为“缓冲区超读 (buffer overread)”,如果攻击者能够操纵索引从而读取存储在数据结构之后的他们不被允许访问的数据,可能会导致安全漏洞。
In C, attempting to read beyond the end of a data structure is undefined behavior. You might get whatever is at the location in memory that would correspond to that element in the data structure, even though the memory doesn’t belong to that structure. This is called a buffer overread and can lead to security vulnerabilities if an attacker is able to manipulate the index in such a way as to read data they shouldn’t be allowed to that is stored after the data structure.
为了保护你的程序免受此类漏洞的影响,如果你尝试读取不存在的索引处的元素,Rust 将停止执行并拒绝继续。让我们试一试:
To protect your program from this sort of vulnerability, if you try to read an element at an index that doesn’t exist, Rust will stop execution and refuse to continue. Let’s try it and see:
{{#include ../listings/ch09-error-handling/listing-09-01/output.txt}}
此错误指向了我们的 main.rs 的第 4 行,即我们尝试访问 v 中向量索引 99 的地方。
This error points at line 4 of our main.rs where we attempt to access index
99 of the vector in v.
note: 行告诉我们可以设置 RUST_BACKTRACE 环境变量来获取导致错误的确切情况的“回溯”。“回溯 (backtrace)”是到达这一点所调用的所有函数的列表。Rust 中的回溯与其他语言中的回溯工作方式相同:阅读回溯的关键是从顶部开始阅读,直到看到你编写的文件。那就是问题起源的地方。该位置上方的行是你的代码调用的代码;下方的行是调用你代码的代码。这些前后行可能包括 Rust 核心代码、标准库代码或你正在使用的 crate。让我们尝试通过将 RUST_BACKTRACE 环境变量设置为除 0 以外的任何值来获取回溯。示例 9-2 显示了你将看到的类似输出。
The note: line tells us that we can set the RUST_BACKTRACE environment
variable to get a backtrace of exactly what happened to cause the error. A
backtrace is a list of all the functions that have been called to get to this
point. Backtraces in Rust work as they do in other languages: The key to
reading the backtrace is to start from the top and read until you see files you
wrote. That’s the spot where the problem originated. The lines above that spot
are code that your code has called; the lines below are code that called your
code. These before-and-after lines might include core Rust code, standard
library code, or crates that you’re using. Let’s try to get a backtrace by
setting the RUST_BACKTRACE environment variable to any value except 0.
Listing 9-2 shows output similar to what you’ll see.
$ RUST_BACKTRACE=1 cargo run
thread 'main' panicked at src/main.rs:4:6:
index out of bounds: the len is 3 but the index is 99
stack backtrace:
0: rust_begin_unwind
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/std/src/panicking.rs:692:5
1: core::panicking::panic_fmt
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:75:14
2: core::panicking::panic_bounds_check
at /rustc/4d91de4e48198da2e33413efdcd9cd2cc0c46688/library/core/src/panicking.rs:273:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:274:10
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/slice/index.rs:16:9
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:3361:9
6: panic::main
at ./src/main.rs:4:6
7: core::ops::function::FnOnce::call_once
at file:///home/.rustup/toolchains/1.85/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
内容真多!你看到的具体输出可能会根据你的操作系统和 Rust 版本而有所不同。为了获得包含此类信息的回溯,必须启用调试符号。当使用 cargo build 或 cargo run 而不带 --release 标志时,调试符号是默认启用的,就像我们这里所做的那样。
That’s a lot of output! The exact output you see might be different depending
on your operating system and Rust version. In order to get backtraces with this
information, debug symbols must be enabled. Debug symbols are enabled by
default when using cargo build or cargo run without the --release flag,
as we have here.
在示例 9-2 的输出中,回溯的第 6 行指向了我们项目中导致问题的行:src/main.rs 的第 4 行。如果我们不想让程序恐慌,我们应该从提到我们编写的文件的第一行所指向的位置开始调查。在示例 9-1 中,我们故意编写了会恐慌的代码,修复恐慌的方法是不请求超出向量索引范围的元素。将来当你的代码发生恐慌时,你需要弄清楚代码正在对哪些值采取什么行动导致了恐慌,以及代码应该采取什么行动。
In the output in Listing 9-2, line 6 of the backtrace points to the line in our project that’s causing the problem: line 4 of src/main.rs. If we don’t want our program to panic, we should start our investigation at the location pointed to by the first line mentioning a file we wrote. In Listing 9-1, where we deliberately wrote code that would panic, the way to fix the panic is to not request an element beyond the range of the vector indexes. When your code panics in the future, you’ll need to figure out what action the code is taking with what values to cause the panic and what the code should do instead.
我们将在本章稍后的“要 panic! 还是不要 panic!”部分回到 panic! 以及我们何时应该或不应该使用 panic! 来处理错误条件。接下来,我们将看看如何使用 Result 从错误中恢复。
We’ll come back to panic! and when we should and should not use panic! to
handle error conditions in the “To panic! or Not to
panic!” section later in this
chapter. Next, we’ll look at how to recover from an error using Result.