|
|
|
|
<!DOCTYPE HTML>
|
|
|
|
|
<html lang="zh-CN" class="sidebar-visible no-js light">
|
|
|
|
|
<head>
|
|
|
|
|
<!-- Book generated using mdBook -->
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<title>panic! 深入剖析 - Rust语言圣经(Rust Course)</title>
|
|
|
|
|
<!-- Custom HTML head -->
|
|
|
|
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
|
|
|
|
<meta name="description" content="">
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
<meta name="theme-color" content="#ffffff" />
|
|
|
|
|
|
|
|
|
|
<link rel="icon" href="../../favicon.svg">
|
|
|
|
|
<link rel="shortcut icon" href="../../favicon.png">
|
|
|
|
|
<link rel="stylesheet" href="../../css/variables.css">
|
|
|
|
|
<link rel="stylesheet" href="../../css/general.css">
|
|
|
|
|
<link rel="stylesheet" href="../../css/chrome.css">
|
|
|
|
|
<link rel="stylesheet" href="../../css/print.css" media="print">
|
|
|
|
|
<!-- Fonts -->
|
|
|
|
|
<link rel="stylesheet" href="../../FontAwesome/css/font-awesome.css">
|
|
|
|
|
<link rel="stylesheet" href="../../fonts/fonts.css">
|
|
|
|
|
<!-- Highlight.js Stylesheets -->
|
|
|
|
|
<link rel="stylesheet" href="../../highlight.css">
|
|
|
|
|
<link rel="stylesheet" href="../../tomorrow-night.css">
|
|
|
|
|
<link rel="stylesheet" href="../../ayu-highlight.css">
|
|
|
|
|
|
|
|
|
|
<!-- Custom theme stylesheets -->
|
|
|
|
|
<link rel="stylesheet" href="../../theme/style.css">
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<!-- Provide site root to javascript -->
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
var path_to_root = "../../";
|
|
|
|
|
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
try {
|
|
|
|
|
var theme = localStorage.getItem('mdbook-theme');
|
|
|
|
|
var sidebar = localStorage.getItem('mdbook-sidebar');
|
|
|
|
|
if (theme.startsWith('"') && theme.endsWith('"')) {
|
|
|
|
|
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
|
|
|
|
}
|
|
|
|
|
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
|
|
|
|
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
|
|
|
|
}
|
|
|
|
|
} catch (e) { }
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<!-- Set the theme before any content is loaded, prevents flash -->
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
var theme;
|
|
|
|
|
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
|
|
|
|
if (theme === null || theme === undefined) { theme = default_theme; }
|
|
|
|
|
var html = document.querySelector('html');
|
|
|
|
|
html.classList.remove('no-js')
|
|
|
|
|
html.classList.remove('light')
|
|
|
|
|
html.classList.add(theme);
|
|
|
|
|
html.classList.add('js');
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<!-- Hide / unhide sidebar before it is displayed -->
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
var html = document.querySelector('html');
|
|
|
|
|
var sidebar = 'hidden';
|
|
|
|
|
if (document.body.clientWidth >= 1080) {
|
|
|
|
|
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
|
|
|
|
sidebar = sidebar || 'visible';
|
|
|
|
|
}
|
|
|
|
|
html.classList.remove('sidebar-visible');
|
|
|
|
|
html.classList.add("sidebar-" + sidebar);
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
|
|
|
|
<div class="sidebar-scrollbox">
|
|
|
|
|
<ol class="chapter"><li class="chapter-item affix "><a href="../../about-book.html">关于本书</a></li><li class="chapter-item affix "><a href="../../into-rust.html">进入 Rust 编程世界</a></li><li class="chapter-item affix "><a href="../../first-try/sth-you-should-not-do.html">避免从入门到放弃</a></li><li class="chapter-item affix "><a href="../../community.html">社区和锈书</a></li><li class="chapter-item affix "><li class="part-title">Rust 语言基础学习</li><li class="spacer"></li><li class="chapter-item "><a href="../../first-try/intro.html"><strong aria-hidden="true">1.</strong> 寻找牛刀,以便小试</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../first-try/installation.html"><strong aria-hidden="true">1.1.</strong> 安装 Rust 环境</a></li><li class="chapter-item "><a href="../../first-try/editor.html"><strong aria-hidden="true">1.2.</strong> 墙推 VSCode!</a></li><li class="chapter-item "><a href="../../first-try/cargo.html"><strong aria-hidden="true">1.3.</strong> 认识 Cargo</a></li><li class="chapter-item "><a href="../../first-try/hello-world.html"><strong aria-hidden="true">1.4.</strong> 不仅仅是 Hello world</a></li><li class="chapter-item "><a href="../../first-try/slowly-downloading.html"><strong aria-hidden="true">1.5.</strong> 下载依赖太慢了?</a></li></ol></li><li class="chapter-item expanded "><a href="../../basic/intro.html"><strong aria-hidden="true">2.</strong> Rust 基础入门</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../basic/variable.html"><strong aria-hidden="true">2.1.</strong> 变量绑定与解构</a></li><li class="chapter-item "><a href="../../basic/base-type/index.html"><strong aria-hidden="true">2.2.</strong> 基本类型</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../basic/base-type/numbers.html"><strong aria-hidden="true">2.2.1.</strong> 数值类型</a></li><li class="chapter-item "><a href="../../basic/base-type/char-bool.html"><strong aria-hidden="true">2.2.2.</strong> 字符、布尔、单元类型</a></li><li class="chapter-item "><a href="../../basic/base-type/statement-expression.html"><strong aria-hidden="true">2.2.3.</strong> 语句与表达式</a></li><li class="chapter-item "><a href="../../basic/base-type/function.html"><strong aria-hidden="true">2.2.4.</strong> 函数</a></li></ol></li><li class="chapter-item "><a href="../../basic/ownership/index.html"><strong aria-hidden="true">2.3.</strong> 所有权和借用</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../basic/ownership/ownership.html"><strong aria-hidden="true">2.3.1.</strong> 所有权</a></li><li class="chapter-item "><a href="../../basic/ownership/borrowing.html"><strong aria-hidden="true">2.3.2.</strong> 引用与借用</a></li></ol></li><li class="chapter-item "><a href="../../basic/compound-type/intro.html"><strong aria-hidden="true">2.4.</strong> 复合类型</a><a class="toggle"><div>❱</div></a></li><li><ol class="section"><li class="chapter-item "><a href="../../basic/compound-type/string-slice.html"><strong aria-hidden="true">2.4.1.</strong> 字符串与切片</a></li><li class="chapter-item "><a href="../../basic/compound-type/tuple.html"><strong aria-hidden="true">2.4.2.</strong> 元组</a></li><li class="chapter-item "><a href="../../basic/compound-type/struct.html"><strong aria-hidden="true">2.4.3.</strong> 结构体</a></li><li class="chapter-item "><a href="../../basic/compound-type/enum.html"><strong aria-hidden="true">2.4.4.</strong> 枚举</a></li><li class="chapter-item "><a href="../../basic/compound-type/array.html"><strong aria-hidden="true">2.4.5.</strong> 数组</a></li></ol></li><li class="chapter-item "><a href="../../basic/flow-control.html"><strong aria-hidden="true">2.5.</strong> 流程控制</a></li><li class="chapter-item "><a href="../../basic/match-pattern/intro.html"><strong a
|
|
|
|
|
</div>
|
|
|
|
|
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
<div id="page-wrapper" class="page-wrapper">
|
|
|
|
|
|
|
|
|
|
<div class="page">
|
|
|
|
|
<div id="menu-bar-hover-placeholder"></div>
|
|
|
|
|
<div id="menu-bar" class="menu-bar sticky bordered">
|
|
|
|
|
<div class="left-buttons">
|
|
|
|
|
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
|
|
|
|
<i class="fa fa-bars"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
|
|
|
|
<i class="fa fa-paint-brush"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
|
|
|
|
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
|
|
|
|
|
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
|
|
|
|
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
|
|
|
|
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
|
|
|
|
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
|
|
|
|
</ul>
|
|
|
|
|
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
|
|
|
|
|
<i class="fa fa-search"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<h1 class="menu-title">Rust语言圣经(Rust Course)</h1>
|
|
|
|
|
|
|
|
|
|
<div class="right-buttons">
|
|
|
|
|
<a href="../../print.html" title="Print this book" aria-label="Print this book">
|
|
|
|
|
<i id="print-button" class="fa fa-print"></i>
|
|
|
|
|
</a>
|
|
|
|
|
<a href="https://github.com/sunface/rust-course" title="Git repository" aria-label="Git repository">
|
|
|
|
|
<i id="git-repository-button" class="fa fa-github"></i>
|
|
|
|
|
</a>
|
|
|
|
|
<a href="https://github.com/sunface/rust-course/edit/main/src/basic/result-error/panic.md" title="Suggest an edit" aria-label="Suggest an edit">
|
|
|
|
|
<i id="git-edit-button" class="fa fa-edit"></i>
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="search-wrapper" class="hidden">
|
|
|
|
|
<form id="searchbar-outer" class="searchbar-outer">
|
|
|
|
|
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
|
|
|
|
</form>
|
|
|
|
|
<div id="searchresults-outer" class="searchresults-outer hidden">
|
|
|
|
|
<div id="searchresults-header" class="searchresults-header"></div>
|
|
|
|
|
<ul id="searchresults">
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
|
|
|
|
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
|
|
|
|
|
|
|
|
|
// Get viewed page store
|
|
|
|
|
var viewed_key = 'mdbook-viewed';
|
|
|
|
|
var viewed_map = {};
|
|
|
|
|
try {
|
|
|
|
|
var viewed_storage = localStorage.getItem(viewed_key);
|
|
|
|
|
if (viewed_storage) {
|
|
|
|
|
viewed_map = JSON.parse(viewed_storage)
|
|
|
|
|
}
|
|
|
|
|
} catch (e) { }
|
|
|
|
|
|
|
|
|
|
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
|
|
|
|
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
|
|
|
|
|
|
|
|
|
// Apply viewed style
|
|
|
|
|
if (viewed_map[link.pathname]) {
|
|
|
|
|
link.classList.add('md-viewed')
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Mark viewed after 30s
|
|
|
|
|
setTimeout(function() {
|
|
|
|
|
viewed_map[location.pathname] = 1;
|
|
|
|
|
localStorage.setItem(viewed_key, JSON.stringify(viewed_map));
|
|
|
|
|
}, 30000)
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<div id="content" class="content">
|
|
|
|
|
<!-- Page table of contents -->
|
|
|
|
|
<div class="sidetoc"><nav class="pagetoc"></nav></div>
|
|
|
|
|
<main>
|
|
|
|
|
<h1 id="panic-深入剖析"><a class="header" href="#panic-深入剖析">panic 深入剖析</a></h1>
|
|
|
|
|
<p>在正式开始之前,先来思考一个问题:假设我们想要从文件读取数据,如果失败,你有没有好的办法通知调用者为何失败?如果成功,你有没有好的办法把读取的结果返还给调用者?</p>
|
|
|
|
|
<h2 id="panic-与不可恢复错误"><a class="header" href="#panic-与不可恢复错误">panic! 与不可恢复错误</a></h2>
|
|
|
|
|
<p>上面的问题在真实场景会经常遇到,其实处理起来挺复杂的,让我们先做一个假设:文件读取操作发生在系统启动阶段。那么可以轻易得出一个结论,一旦文件读取失败,那么系统启动也将失败,这意味着该失败是不可恢复的错误,无论是因为文件不存在还是操作系统硬盘的问题,这些只是错误的原因不同,但是归根到底都是不可恢复的错误(梳理清楚当前场景的错误类型非常重要)。</p>
|
|
|
|
|
<p>对于这些严重到影响程序运行的错误,触发 <code>panic</code> 是很好的解决方式。在 Rust 中触发 <code>panic</code> 有两种方式:被动触发和主动调用,下面依次来看看。</p>
|
|
|
|
|
<h3 id="被动触发"><a class="header" href="#被动触发">被动触发</a></h3>
|
|
|
|
|
<p>先来看一段简单又熟悉的代码:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
|
|
|
|
|
let v = vec![1, 2, 3];
|
|
|
|
|
|
|
|
|
|
v[99];
|
|
|
|
|
}
|
|
|
|
|
</code></pre></pre>
|
|
|
|
|
<p>心明眼亮的同学立马就能看出这里发生了严重的错误 —— 数组访问越界,在其它编程语言中无一例外,都会报出严重的异常,甚至导致程序直接崩溃关闭。</p>
|
|
|
|
|
<p>而 Rust 也不例外,运行后将看到如下报错:</p>
|
|
|
|
|
<pre><code class="language-shell">$ cargo run
|
|
|
|
|
Compiling panic v0.1.0 (file:///projects/panic)
|
|
|
|
|
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
|
|
|
|
|
Running `target/debug/panic`
|
|
|
|
|
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
|
|
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>上面给出了非常详细的报错信息,包含了具体的异常描述以及发生的位置,甚至你还可以加入额外的命令来看到异常发生时的堆栈信息,这个会在后面详细展开。</p>
|
|
|
|
|
<p>总之,类似的 <code>panic</code> 还有很多,而被动触发的 <code>panic</code> 是我们日常开发中最常遇到的,这也是 Rust 给我们的一种保护,毕竟错误只有抛出来,才有可能被处理,否则只会偷偷隐藏起来,寻觅时机给你致命一击。</p>
|
|
|
|
|
<h3 id="主动调用"><a class="header" href="#主动调用">主动调用</a></h3>
|
|
|
|
|
<p>在某些特殊场景中,开发者想要主动抛出一个异常,例如开头提到的在系统启动阶段读取文件失败。</p>
|
|
|
|
|
<p>对此,Rust 为我们提供了 <code>panic!</code> 宏,当调用执行该宏时,<strong>程序会打印出一个错误信息,展开报错点往前的函数调用堆栈,最后退出程序</strong>。</p>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p>切记,一定是不可恢复的错误,才调用 <code>panic!</code> 处理,你总不想系统仅仅因为用户随便传入一个非法参数就崩溃吧?所以,<strong>只有当你不知道该如何处理时,再去调用 panic!</strong>.</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
<p>首先,来调用一下 <code>panic!</code>,这里使用了最简单的代码实现,实际上你在程序的任何地方都可以这样调用:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
|
|
|
|
|
panic!("crash and burn");
|
|
|
|
|
}
|
|
|
|
|
</code></pre></pre>
|
|
|
|
|
<p>运行后输出:</p>
|
|
|
|
|
<pre><code class="language-console">thread 'main' panicked at 'crash and burn', src/main.rs:2:5
|
|
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>以上信息包含了两条重要信息:</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li><code>main</code> 函数所在的线程崩溃了,发生的代码位置是 <code>src/main.rs</code> 中的第 2 行第 5 个字符(去除该行前面的空字符)</li>
|
|
|
|
|
<li>在使用时加上一个环境变量可以获取更详细的栈展开信息:
|
|
|
|
|
<ul>
|
|
|
|
|
<li>Linux/macOS 等 UNIX 系统: <code>RUST_BACKTRACE=1 cargo run</code></li>
|
|
|
|
|
<li>Windows 系统(PowerShell): <code>$env:RUST_BACKTRACE=1 ; cargo run</code></li>
|
|
|
|
|
</ul>
|
|
|
|
|
</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<p>下面让我们针对第二点进行详细展开讲解。</p>
|
|
|
|
|
<h2 id="backtrace-栈展开"><a class="header" href="#backtrace-栈展开">backtrace 栈展开</a></h2>
|
|
|
|
|
<p>在真实场景中,错误往往涉及到很长的调用链甚至会深入第三方库,如果没有栈展开技术,错误将难以跟踪处理,下面我们来看一个真实的崩溃例子:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
|
|
|
|
|
let v = vec![1, 2, 3];
|
|
|
|
|
|
|
|
|
|
v[99];
|
|
|
|
|
}
|
|
|
|
|
</code></pre></pre>
|
|
|
|
|
<p>上面的代码很简单,数组只有 <code>3</code> 个元素,我们却尝试去访问它的第 <code>100</code> 号元素(数组索引从 <code>0</code> 开始),那自然会崩溃。</p>
|
|
|
|
|
<p>我们的读者里不乏正义之士,此时肯定要质疑,一个简单的数组越界访问,为何要直接让程序崩溃?是不是有些小题大作了?</p>
|
|
|
|
|
<p>如果有过 C 语言的经验,即使你越界了,问题不大,我依然尝试去访问,至于这个值是不是你想要的(<code>100</code> 号内存地址也有可能有值,只不过是其它变量或者程序的!),抱歉,不归我管,我只负责取,你要负责管理好自己的索引访问范围。上面这种情况被称为<strong>缓冲区溢出</strong>,并可能会导致安全漏洞,例如攻击者可以通过索引来访问到数组后面不被允许的数据。</p>
|
|
|
|
|
<p>说实话,我宁愿程序崩溃,为什么?当你取到了一个不属于你的值,这在很多时候会导致程序上的逻辑 BUG! 有编程经验的人都知道这种逻辑上的 BUG 是多么难被发现和修复!因此程序直接崩溃,然后告诉我们问题发生的位置,最后我们对此进行修复,这才是最合理的软件开发流程,而不是把问题藏着掖着:</p>
|
|
|
|
|
<pre><code class="language-console">thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
|
|
|
|
|
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>好的,现在成功知道问题发生的位置,但是如果我们想知道该问题之前经过了哪些调用环节,该怎么办?那就按照提示使用 <code>RUST_BACKTRACE=1 cargo run</code> 或 <code>$env:RUST_BACKTRACE=1 ; cargo run</code> 来再一次运行程序:</p>
|
|
|
|
|
<pre><code class="language-console">thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
|
|
|
|
|
stack backtrace:
|
|
|
|
|
0: rust_begin_unwind
|
|
|
|
|
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/std/src/panicking.rs:517:5
|
|
|
|
|
1: core::panicking::panic_fmt
|
|
|
|
|
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/panicking.rs:101:14
|
|
|
|
|
2: core::panicking::panic_bounds_check
|
|
|
|
|
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/panicking.rs:77:5
|
|
|
|
|
3: <usize as core::slice::index::SliceIndex<[T]>>::index
|
|
|
|
|
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/slice/index.rs:184:10
|
|
|
|
|
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
|
|
|
|
|
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/slice/index.rs:15:9
|
|
|
|
|
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
|
|
|
|
|
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/alloc/src/vec/mod.rs:2465:9
|
|
|
|
|
6: world_hello::main
|
|
|
|
|
at ./src/main.rs:4:5
|
|
|
|
|
7: core::ops::function::FnOnce::call_once
|
|
|
|
|
at /rustc/59eed8a2aac0230a8b53e89d4e99d55912ba6b35/library/core/src/ops/function.rs:227:5
|
|
|
|
|
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
|
|
|
|
|
</code></pre>
|
|
|
|
|
<p>上面的代码就是一次栈展开(也称栈回溯),它包含了函数调用的顺序,当然按照逆序排列:最近调用的函数排在列表的最上方。因为咱们的 <code>main</code> 函数基本是最先调用的函数了,所以排在了倒数第二位,还有一个关注点,排在最顶部最后一个调用的函数是 <code>rust_begin_unwind</code>,该函数的目的就是进行栈展开,呈现这些列表信息给我们。</p>
|
|
|
|
|
<p>要获取到栈回溯信息,你还需要开启 <code>debug</code> 标志,该标志在使用 <code>cargo run</code> 或者 <code>cargo build</code> 时自动开启(这两个操作默认是 <code>Debug</code> 运行方式)。同时,栈展开信息在不同操作系统或者 Rust 版本上也有所不同。</p>
|
|
|
|
|
<h2 id="panic-时的两种终止方式"><a class="header" href="#panic-时的两种终止方式">panic 时的两种终止方式</a></h2>
|
|
|
|
|
<p>当出现 <code>panic!</code> 时,程序提供了两种方式来处理终止流程:<strong>栈展开</strong>和<strong>直接终止</strong>。</p>
|
|
|
|
|
<p>其中,默认的方式就是 <code>栈展开</code>,这意味着 Rust 会回溯栈上数据和函数调用,因此也意味着更多的善后工作,好处是可以给出充分的报错信息和栈调用信息,便于事后的问题复盘。<code>直接终止</code>,顾名思义,不清理数据就直接退出程序,善后工作交与操作系统来负责。</p>
|
|
|
|
|
<p>对于绝大多数用户,使用默认选择是最好的,但是当你关心最终编译出的二进制可执行文件大小时,那么可以尝试去使用直接终止的方式,例如下面的配置修改 <code>Cargo.toml</code> 文件,实现在 <a href="https://course.rs/first-try/cargo.html#%E6%89%8B%E5%8A%A8%E7%BC%96%E8%AF%91%E5%92%8C%E8%BF%90%E8%A1%8C%E9%A1%B9%E7%9B%AE"><code>release</code></a> 模式下遇到 <code>panic</code> 直接终止:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
|
|
|
<span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>[profile.release]
|
|
|
|
|
panic = 'abort'
|
|
|
|
|
<span class="boring">}
|
|
|
|
|
</span></code></pre></pre>
|
|
|
|
|
<h2 id="线程-panic-后程序是否会终止"><a class="header" href="#线程-panic-后程序是否会终止">线程 <code>panic</code> 后,程序是否会终止?</a></h2>
|
|
|
|
|
<p>长话短说,如果是 <code>main</code> 线程,则程序会终止,如果是其它子线程,该线程会终止,但是不会影响 <code>main</code> 线程。因此,尽量不要在 <code>main</code> 线程中做太多任务,将这些任务交由子线程去做,就算子线程 <code>panic</code> 也不会导致整个程序的结束。</p>
|
|
|
|
|
<p>具体解析见 <a href="#panic-%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90">panic 原理剖析</a>。</p>
|
|
|
|
|
<h2 id="何时该使用-panic"><a class="header" href="#何时该使用-panic">何时该使用 panic!</a></h2>
|
|
|
|
|
<p>下面让我们大概罗列下何时适合使用 <code>panic</code>,也许经过之前的学习,你已经能够对 <code>panic</code> 的使用有了自己的看法,但是我们还是会罗列一些常见的用法来加深你的理解。</p>
|
|
|
|
|
<p>先来一点背景知识,在前面章节我们粗略讲过 <code>Result<T, E></code> 这个枚举类型,它是用来表示函数的返回结果:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
|
|
|
<span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>enum Result<T, E> {
|
|
|
|
|
Ok(T),
|
|
|
|
|
Err(E),
|
|
|
|
|
}
|
|
|
|
|
<span class="boring">}
|
|
|
|
|
</span></code></pre></pre>
|
|
|
|
|
<p>当没有错误发生时,函数返回一个用 <code>Result</code> 类型包裹的值 <code>Ok(T)</code>,当错误时,返回一个 <code>Err(E)</code>。对于 <code>Result</code> 返回我们有很多处理方法,最简单粗暴的就是 <code>unwrap</code> 和 <code>expect</code>,这两个函数非常类似,我们以 <code>unwrap</code> 举例:</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
|
|
|
<span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>use std::net::IpAddr;
|
|
|
|
|
let home: IpAddr = "127.0.0.1".parse().unwrap();
|
|
|
|
|
<span class="boring">}
|
|
|
|
|
</span></code></pre></pre>
|
|
|
|
|
<p>上面的 <code>parse</code> 方法试图将字符串 <code>"127.0.0.1" </code>解析为一个 IP 地址类型 <code>IpAddr</code>,它返回一个 <code>Result<IpAddr, E></code> 类型,如果解析成功,则把 <code>Ok(IpAddr)</code> 中的值赋给 <code>home</code>,如果失败,则不处理 <code>Err(E)</code>,而是直接 <code>panic</code>。</p>
|
|
|
|
|
<p>因此 <code>unwrap</code> 简而言之:成功则返回值,失败则 <code>panic</code>,总之不进行任何错误处理。</p>
|
|
|
|
|
<h4 id="示例原型测试"><a class="header" href="#示例原型测试">示例、原型、测试</a></h4>
|
|
|
|
|
<p>这几个场景下,需要快速地搭建代码,错误处理会拖慢编码的速度,也不是特别有必要,因此通过 <code>unwrap</code>、<code>expect</code> 等方法来处理是最快的。</p>
|
|
|
|
|
<p>同时,当我们回头准备做错误处理时,可以全局搜索这些方法,不遗漏地进行替换。</p>
|
|
|
|
|
<h4 id="你确切的知道你的程序是正确时可以使用-panic"><a class="header" href="#你确切的知道你的程序是正确时可以使用-panic">你确切的知道你的程序是正确时,可以使用 panic</a></h4>
|
|
|
|
|
<p>因为 <code>panic</code> 的触发方式比错误处理要简单,因此可以让代码更清晰,可读性也更加好,当我们的代码注定是正确时,你可以用 <code>unwrap</code> 等方法直接进行处理,反正也不可能 <code>panic</code> :</p>
|
|
|
|
|
<pre><pre class="playground"><code class="language-rust edition2021">
|
|
|
|
|
<span class="boring">#![allow(unused)]
|
|
|
|
|
</span><span class="boring">fn main() {
|
|
|
|
|
</span>use std::net::IpAddr;
|
|
|
|
|
let home: IpAddr = "127.0.0.1".parse().unwrap();
|
|
|
|
|
<span class="boring">}
|
|
|
|
|
</span></code></pre></pre>
|
|
|
|
|
<p>例如上面的例子,<code>"127.0.0.1"</code> 就是 <code>ip</code> 地址,因此我们知道 <code>parse</code> 方法一定会成功,那么就可以直接用 <code>unwrap</code> 方法进行处理。</p>
|
|
|
|
|
<p>当然,如果该字符串是来自于用户输入,那在实际项目中,就必须用错误处理的方式,而不是 <code>unwrap</code>,否则你的程序一天要崩溃几十万次吧!</p>
|
|
|
|
|
<h4 id="可能导致全局有害状态时"><a class="header" href="#可能导致全局有害状态时">可能导致全局有害状态时</a></h4>
|
|
|
|
|
<p>有害状态大概分为几类:</p>
|
|
|
|
|
<ul>
|
|
|
|
|
<li>非预期的错误</li>
|
|
|
|
|
<li>后续代码的运行会受到显著影响</li>
|
|
|
|
|
<li>内存安全的问题</li>
|
|
|
|
|
</ul>
|
|
|
|
|
<p>当错误预期会出现时,返回一个错误较为合适,例如解析器接收到格式错误的数据,HTTP 请求接收到错误的参数甚至该请求内的任何错误(不会导致整个程序有问题,只影响该次请求)。<strong>因为错误是可预期的,因此也是可以处理的</strong>。</p>
|
|
|
|
|
<p>当启动时某个流程发生了错误,对后续代码的运行造成了影响,那么就应该使用 <code>panic</code>,而不是处理错误后继续运行,当然你可以通过重试的方式来继续。</p>
|
|
|
|
|
<p>上面提到过,数组访问越界,就要 <code>panic</code> 的原因,这个就是属于内存安全的范畴,一旦内存访问不安全,那么我们就无法保证自己的程序会怎么运行下去,也无法保证逻辑和数据的正确性。</p>
|
|
|
|
|
<h2 id="panic-原理剖析"><a class="header" href="#panic-原理剖析">panic 原理剖析</a></h2>
|
|
|
|
|
<p>本来不想写这块儿内容,因为真的难写,但是转念一想,既然号称圣经,那么本书就得与众不同,避重就轻显然不是该有的态度。</p>
|
|
|
|
|
<p>当调用 <code>panic!</code> 宏时,它会</p>
|
|
|
|
|
<ol>
|
|
|
|
|
<li>格式化 <code>panic</code> 信息,然后使用该信息作为参数,调用 <code>std::panic::panic_any()</code> 函数</li>
|
|
|
|
|
<li><code>panic_any</code> 会检查应用是否使用了 <a href="https://doc.rust-lang.org/std/panic/fn.set_hook.html"><code>panic hook</code></a>,如果使用了,该 <code>hook</code> 函数就会被调用(<code>hook</code> 是一个钩子函数,是外部代码设置的,用于在 <code>panic</code> 触发时,执行外部代码所需的功能)</li>
|
|
|
|
|
<li>当 <code>hook</code> 函数返回后,当前的线程就开始进行栈展开:从 <code>panic_any</code> 开始,如果寄存器或者栈因为某些原因信息错乱了,那很可能该展开会发生异常,最终线程会直接停止,展开也无法继续进行</li>
|
|
|
|
|
<li>展开的过程是一帧一帧的去回溯整个栈,每个帧的数据都会随之被丢弃,但是在展开过程中,你可能会遇到被用户标记为 <code>catching</code> 的帧(通过 <code>std::panic::catch_unwind()</code> 函数标记),此时用户提供的 <code>catch</code> 函数会被调用,展开也随之停止:当然,如果 <code>catch</code> 选择在内部调用 <code>std::panic::resume_unwind()</code> 函数,则展开还会继续。</li>
|
|
|
|
|
</ol>
|
|
|
|
|
<p>还有一种情况,在展开过程中,如果展开本身 <code>panic</code> 了,那展开线程会终止,展开也随之停止。</p>
|
|
|
|
|
<p>一旦线程展开被终止或者完成,最终的输出结果是取决于哪个线程 <code>panic</code>:对于 <code>main</code> 线程,操作系统提供的终止功能 <code>core::intrinsics::abort()</code> 会被调用,最终结束当前的 <code>panic</code> 进程;如果是其它子线程,那么子线程就会简单的终止,同时信息会在稍后通过 <code>std::thread::join()</code> 进行收集。</p>
|
|
|
|
|
<h2 id="课后练习"><a class="header" href="#课后练习">课后练习</a></h2>
|
|
|
|
|
<blockquote>
|
|
|
|
|
<p><a href="https://zh.practice.rs/result-panic/panic.html">Rust By Practice</a>,支持代码在线编辑和运行,并提供详细的<a href="https://github.com/sunface/rust-by-practice/blob/master/solutions/result-panic/panic.md">习题解答</a>。</p>
|
|
|
|
|
</blockquote>
|
|
|
|
|
|
|
|
|
|
<div id="giscus-container"></div>
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<nav class="nav-wrapper" aria-label="Page navigation">
|
|
|
|
|
<!-- Mobile navigation buttons -->
|
|
|
|
|
<a rel="prev" href="../../basic/result-error/intro.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
|
|
|
<i class="fa fa-angle-left"></i>
|
|
|
|
|
</a>
|
|
|
|
|
<a rel="next" href="../../basic/result-error/result.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
|
|
|
<i class="fa fa-angle-right"></i>
|
|
|
|
|
</a>
|
|
|
|
|
<div style="clear: both"></div>
|
|
|
|
|
</nav>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
|
|
|
|
<a rel="prev" href="../../basic/result-error/intro.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
|
|
|
|
|
<i class="fa fa-angle-left"></i>
|
|
|
|
|
</a>
|
|
|
|
|
<a rel="next" href="../../basic/result-error/result.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
|
|
|
|
|
<i class="fa fa-angle-right"></i>
|
|
|
|
|
</a>
|
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
window.playground_copyable = true;
|
|
|
|
|
</script>
|
|
|
|
|
<script src="../../ace.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../editor.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../mode-rust.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../theme-dawn.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../theme-tomorrow_night.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../mark.min.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../searcher.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../highlight.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script src="../../book.js" type="text/javascript" charset="utf-8"></script>
|
|
|
|
|
<script type="text/javascript" charset="utf-8">
|
|
|
|
|
var pagePath = "basic/result-error/panic.md"
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!-- Custom JS scripts -->
|
|
|
|
|
<script type="text/javascript" src="../../assets/custom.js"></script>
|
|
|
|
|
<script type="text/javascript" src="../../assets/bigPicture.js"></script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|