You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

818 lines
87 KiB

1 month ago
<!DOCTYPE HTML>
<html lang="zh-CN" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>错误处理 - Rust语言圣经(Rust Course)</title>
<!-- Custom HTML head -->
<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 class="sidebar-visible no-js">
<div id="body-container">
<!-- Provide site root to javascript -->
<script>
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>
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>
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('light')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.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="spacer"></li><li class="chapter-item affix "><a href="../some-thoughts.html">Xobserve: 一切皆可观测</a></li><li class="chapter-item affix "><a href="../beat-ai.html">BeatAI: 工程师 AI 入门圣经</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 "><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 a
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<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">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<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</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/advance/errors.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>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<!-- Page table of contents -->
<div class="sidetoc"><nav class="pagetoc"></nav></div>
<main>
<h1 id="错误处理"><a class="header" href="#错误处理">错误处理</a></h1>
<p>在之前的<a href="https://course.rs/basic/result-error/intro.html">返回值和错误处理</a>章节中,我们学习了几个重要的概念,例如 <code>Result</code> 用于返回结果处理,<code>?</code> 用于错误的传播,若大家对此还较为模糊,强烈建议回头温习下。</p>
<p>在本章节中一起来看看如何对 <code>Result</code> ( <code>Option</code> ) 做进一步的处理,以及如何定义自己的错误类型。</p>
<h2 id="组合器"><a class="header" href="#组合器">组合器</a></h2>
<p>在设计模式中,有一个组合器模式,相信有 Java 背景的同学对此并不陌生。</p>
<blockquote>
<p>将对象组合成树形结构以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。GoF &lt;&lt;设计模式&gt;&gt;</p>
</blockquote>
<p>与组合器模式有所不同,在 Rust 中,组合器更多的是用于对返回结果的类型进行变换:例如使用 <code>ok_or</code> 将一个 <code>Option</code> 类型转换成 <code>Result</code> 类型。</p>
<p>下面我们来看看一些常见的组合器。</p>
<h4 id="or-和-and"><a class="header" href="#or-和-and">or() 和 and()</a></h4>
<p>跟布尔关系的与/或很像,这两个方法会对两个表达式做逻辑组合,最终返回 <code>Option</code> / <code>Result</code></p>
<ul>
<li><code>or()</code>,表达式按照顺序求值,若任何一个表达式的结果是 <code>Some</code><code>Ok</code>,则该值会立刻返回</li>
<li><code>and()</code>,若两个表达式的结果都是 <code>Some</code><code>Ok</code>,则<strong>第二个表达式中的值被返回</strong>。若任何一个的结果是 <code>None</code><code>Err</code> ,则立刻返回。</li>
</ul>
<p>实际上,只要将布尔表达式的 <code>true</code> / <code>false</code>,替换成 <code>Some</code> / <code>None</code><code>Ok</code> / <code>Err</code> 就很好理解了。</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
let s1 = Some(&quot;some1&quot;);
let s2 = Some(&quot;some2&quot;);
let n: Option&lt;&amp;str&gt; = None;
let o1: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;ok1&quot;);
let o2: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;ok2&quot;);
let e1: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;error1&quot;);
let e2: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;error2&quot;);
assert_eq!(s1.or(s2), s1); // Some1 or Some2 = Some1
assert_eq!(s1.or(n), s1); // Some or None = Some
assert_eq!(n.or(s1), s1); // None or Some = Some
assert_eq!(n.or(n), n); // None1 or None2 = None2
assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 = Ok1
assert_eq!(o1.or(e1), o1); // Ok or Err = Ok
assert_eq!(e1.or(o1), o1); // Err or Ok = Ok
assert_eq!(e1.or(e2), e2); // Err1 or Err2 = Err2
assert_eq!(s1.and(s2), s2); // Some1 and Some2 = Some2
assert_eq!(s1.and(n), n); // Some and None = None
assert_eq!(n.and(s1), n); // None and Some = None
assert_eq!(n.and(n), n); // None1 and None2 = None1
assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 = Ok2
assert_eq!(o1.and(e1), e1); // Ok and Err = Err
assert_eq!(e1.and(o1), e1); // Err and Ok = Err
assert_eq!(e1.and(e2), e1); // Err1 and Err2 = Err1
}</code></pre></pre>
<p>除了 <code>or</code><code>and</code> 之外Rust 还为我们提供了 <code>xor</code> ,但是它只能应用在 <code>Option</code> 上,其实想想也是这个理,如果能应用在 <code>Result</code> 上,那你又该如何对一个值和错误进行异或操作?</p>
<h4 id="or_else-和-and_then"><a class="header" href="#or_else-和-and_then">or_else() 和 and_then()</a></h4>
<p>它们跟 <code>or()</code><code>and()</code> 类似,唯一的区别在于,它们的第二个表达式是一个闭包。</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
// or_else with Option
let s1 = Some(&quot;some1&quot;);
let s2 = Some(&quot;some2&quot;);
let fn_some = || Some(&quot;some2&quot;); // 类似于: let fn_some = || -&gt; Option&lt;&amp;str&gt; { Some(&quot;some2&quot;) };
let n: Option&lt;&amp;str&gt; = None;
let fn_none = || None;
assert_eq!(s1.or_else(fn_some), s1); // Some1 or_else Some2 = Some1
assert_eq!(s1.or_else(fn_none), s1); // Some or_else None = Some
assert_eq!(n.or_else(fn_some), s2); // None or_else Some = Some
assert_eq!(n.or_else(fn_none), None); // None1 or_else None2 = None2
// or_else with Result
let o1: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;ok1&quot;);
let o2: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;ok2&quot;);
let fn_ok = |_| Ok(&quot;ok2&quot;); // 类似于: let fn_ok = |_| -&gt; Result&lt;&amp;str, &amp;str&gt; { Ok(&quot;ok2&quot;) };
let e1: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;error1&quot;);
let e2: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;error2&quot;);
let fn_err = |_| Err(&quot;error2&quot;);
assert_eq!(o1.or_else(fn_ok), o1); // Ok1 or_else Ok2 = Ok1
assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err = Ok
assert_eq!(e1.or_else(fn_ok), o2); // Err or_else Ok = Ok
assert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 = Err2
}</code></pre></pre>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
// and_then with Option
let s1 = Some(&quot;some1&quot;);
let s2 = Some(&quot;some2&quot;);
let fn_some = |_| Some(&quot;some2&quot;); // 类似于: let fn_some = |_| -&gt; Option&lt;&amp;str&gt; { Some(&quot;some2&quot;) };
let n: Option&lt;&amp;str&gt; = None;
let fn_none = |_| None;
assert_eq!(s1.and_then(fn_some), s2); // Some1 and_then Some2 = Some2
assert_eq!(s1.and_then(fn_none), n); // Some and_then None = None
assert_eq!(n.and_then(fn_some), n); // None and_then Some = None
assert_eq!(n.and_then(fn_none), n); // None1 and_then None2 = None1
// and_then with Result
let o1: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;ok1&quot;);
let o2: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;ok2&quot;);
let fn_ok = |_| Ok(&quot;ok2&quot;); // 类似于: let fn_ok = |_| -&gt; Result&lt;&amp;str, &amp;str&gt; { Ok(&quot;ok2&quot;) };
let e1: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;error1&quot;);
let e2: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;error2&quot;);
let fn_err = |_| Err(&quot;error2&quot;);
assert_eq!(o1.and_then(fn_ok), o2); // Ok1 and_then Ok2 = Ok2
assert_eq!(o1.and_then(fn_err), e2); // Ok and_then Err = Err
assert_eq!(e1.and_then(fn_ok), e1); // Err and_then Ok = Err
assert_eq!(e1.and_then(fn_err), e1); // Err1 and_then Err2 = Err1
}</code></pre></pre>
<h4 id="filter"><a class="header" href="#filter">filter</a></h4>
<p><code>filter</code> 用于对 <code>Option</code> 进行过滤:</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
let s1 = Some(3);
let s2 = Some(6);
let n = None;
let fn_is_even = |x: &amp;i8| x % 2 == 0;
assert_eq!(s1.filter(fn_is_even), n); // Some(3) -&gt; 3 is not even -&gt; None
assert_eq!(s2.filter(fn_is_even), s2); // Some(6) -&gt; 6 is even -&gt; Some(6)
assert_eq!(n.filter(fn_is_even), n); // None -&gt; no value -&gt; None
}</code></pre></pre>
<h4 id="map-和-map_err"><a class="header" href="#map-和-map_err">map() 和 map_err()</a></h4>
<p><code>map</code> 可以将 <code>Some</code><code>Ok</code> 中的值映射为另一个:</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
let s1 = Some(&quot;abcde&quot;);
let s2 = Some(5);
let n1: Option&lt;&amp;str&gt; = None;
let n2: Option&lt;usize&gt; = None;
let o1: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;abcde&quot;);
let o2: Result&lt;usize, &amp;str&gt; = Ok(5);
let e1: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;abcde&quot;);
let e2: Result&lt;usize, &amp;str&gt; = Err(&quot;abcde&quot;);
let fn_character_count = |s: &amp;str| s.chars().count();
assert_eq!(s1.map(fn_character_count), s2); // Some1 map = Some2
assert_eq!(n1.map(fn_character_count), n2); // None1 map = None2
assert_eq!(o1.map(fn_character_count), o2); // Ok1 map = Ok2
assert_eq!(e1.map(fn_character_count), e2); // Err1 map = Err2
}</code></pre></pre>
<p>但是如果你想要将 <code>Err</code> 中的值进行改变, <code>map</code> 就无能为力了,此时我们需要用 <code>map_err</code></p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
let o1: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;abcde&quot;);
let o2: Result&lt;&amp;str, isize&gt; = Ok(&quot;abcde&quot;);
let e1: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;404&quot;);
let e2: Result&lt;&amp;str, isize&gt; = Err(404);
let fn_character_count = |s: &amp;str| -&gt; isize { s.parse().unwrap() }; // 该函数返回一个 isize
assert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map = Ok2
assert_eq!(e1.map_err(fn_character_count), e2); // Err1 map = Err2
}</code></pre></pre>
<p>通过对 <code>o1</code> 的操作可以看出,与 <code>map</code> 面对 <code>Err</code> 时的短小类似, <code>map_err</code> 面对 <code>Ok</code> 时也是相当无力的。</p>
<h4 id="map_or-和-map_or_else"><a class="header" href="#map_or-和-map_or_else">map_or() 和 map_or_else()</a></h4>
<p><code>map_or</code><code>map</code> 的基础上提供了一个默认值:</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
const V_DEFAULT: u32 = 1;
let s: Result&lt;u32, ()&gt; = Ok(10);
let n: Option&lt;u32&gt; = None;
let fn_closure = |v: u32| v + 2;
assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12);
assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT);
}</code></pre></pre>
<p>如上所示,当处理 <code>None</code> 的时候,<code>V_DEFAULT</code> 作为默认值被直接返回。</p>
<p><code>map_or_else</code><code>map_or</code> 类似,但是它是通过一个闭包来提供默认值:</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
let s = Some(10);
let n: Option&lt;i8&gt; = None;
let fn_closure = |v: i8| v + 2;
let fn_default = || 1;
assert_eq!(s.map_or_else(fn_default, fn_closure), 12);
assert_eq!(n.map_or_else(fn_default, fn_closure), 1);
let o = Ok(10);
let e = Err(5);
let fn_default_for_result = |v: i8| v + 1; // 闭包可以对 Err 中的值进行处理,并返回一个新值
assert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);
assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6);
}</code></pre></pre>
<h4 id="ok_or-and-ok_or_else"><a class="header" href="#ok_or-and-ok_or_else">ok_or() and ok_or_else()</a></h4>
<p>这两兄弟可以将 <code>Option</code> 类型转换为 <code>Result</code> 类型。其中 <code>ok_or</code> 接收一个默认的 <code>Err</code> 参数:</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
const ERR_DEFAULT: &amp;str = &quot;error message&quot;;
let s = Some(&quot;abcde&quot;);
let n: Option&lt;&amp;str&gt; = None;
let o: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;abcde&quot;);
let e: Result&lt;&amp;str, &amp;str&gt; = Err(ERR_DEFAULT);
assert_eq!(s.ok_or(ERR_DEFAULT), o); // Some(T) -&gt; Ok(T)
assert_eq!(n.ok_or(ERR_DEFAULT), e); // None -&gt; Err(default)
}</code></pre></pre>
<p><code>ok_or_else</code> 接收一个闭包作为 <code>Err</code> 参数:</p>
<pre><pre class="playground"><code class="language-rust edition2021">fn main() {
let s = Some(&quot;abcde&quot;);
let n: Option&lt;&amp;str&gt; = None;
let fn_err_message = || &quot;error message&quot;;
let o: Result&lt;&amp;str, &amp;str&gt; = Ok(&quot;abcde&quot;);
let e: Result&lt;&amp;str, &amp;str&gt; = Err(&quot;error message&quot;);
assert_eq!(s.ok_or_else(fn_err_message), o); // Some(T) -&gt; Ok(T)
assert_eq!(n.ok_or_else(fn_err_message), e); // None -&gt; Err(default)
}</code></pre></pre>
<p>以上列出的只是常用的一部分,强烈建议大家看看标准库中有哪些可用的 API在实际项目中这些 API 将会非常有用: <a href="https://doc.rust-lang.org/stable/std/option/enum.Option.html">Option</a><a href="https://doc.rust-lang.org/stable/std/result/enum.Result.html">Result</a></p>
<h2 id="自定义错误类型"><a class="header" href="#自定义错误类型">自定义错误类型</a></h2>
<p>虽然标准库定义了大量的错误类型,但是一个严谨的项目,光使用这些错误类型往往是不够的,例如我们可能会为暴露给用户的错误定义相应的类型。</p>
<p>为了帮助我们更好的定义错误Rust 在标准库中提供了一些可复用的特征,例如 <code>std::error::Error</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::fmt::{Debug, Display};
pub trait Error: Debug + Display {
fn source(&amp;self) -&gt; Option&lt;&amp;(Error + 'static)&gt; { ... }
}
<span class="boring">}</span></code></pre></pre>
<p>当自定义类型实现该特征后,该类型就可以作为 <code>Err</code> 来使用,下面一起来看看。</p>
<blockquote>
<p>实际上,自定义错误类型只需要实现 <code>Debug</code><code>Display</code> 特征即可,<code>source</code> 方法是可选的,而 <code>Debug</code> 特征往往也无需手动实现,可以直接通过 <code>derive</code> 来派生</p>
</blockquote>
<h4 id="最简单的错误"><a class="header" href="#最简单的错误">最简单的错误</a></h4>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fmt;
// AppError 是自定义错误类型,它可以是当前包中定义的任何类型,在这里为了简化,我们使用了单元结构体作为例子。
// 为 AppError 自动派生 Debug 特征
#[derive(Debug)]
struct AppError;
// 为 AppError 实现 std::fmt::Display 特征
impl fmt::Display for AppError {
fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {
write!(f, &quot;An Error Occurred, Please Try Again!&quot;) // user-facing output
}
}
// 一个示例函数用于产生 AppError 错误
fn produce_error() -&gt; Result&lt;(), AppError&gt; {
Err(AppError)
}
fn main(){
match produce_error() {
Err(e) =&gt; eprintln!(&quot;{}&quot;, e),
_ =&gt; println!(&quot;No error&quot;),
}
eprintln!(&quot;{:?}&quot;, produce_error()); // Err({ file: src/main.rs, line: 17 })
}</code></pre></pre>
<p>上面的例子很简单,我们定义了一个错误类型,当为它派生了 <code>Debug</code> 特征,同时手动实现了 <code>Display</code> 特征后,该错误类型就可以作为 <code>Err</code>来使用了。</p>
<p>事实上,实现 <code>Debug</code><code>Display</code> 特征并不是作为 <code>Err</code> 使用的必要条件,大家可以把这两个特征实现和相应使用去除,然后看看代码会否报错。既然如此,我们为何要为自定义类型实现这两个特征呢?原因有二:</p>
<ul>
<li>错误得打印输出后,才能有实际用处,而打印输出就需要实现这两个特征</li>
<li>可以将自定义错误转换成 <code>Box&lt;dyn std::error:Error&gt;</code> 特征对象,在后面的<strong>归一化不同错误类型</strong>部分,我们会详细介绍</li>
</ul>
<h4 id="更详尽的错误"><a class="header" href="#更详尽的错误">更详尽的错误</a></h4>
<p>上一个例子中定义的错误非常简单,我们无法从错误中得到更多的信息,现在再来定义一个具有错误码和信息的错误:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fmt;
struct AppError {
code: usize,
message: String,
}
// 根据错误码显示不同的错误信息
impl fmt::Display for AppError {
fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {
let err_msg = match self.code {
404 =&gt; &quot;Sorry, Can not find the Page!&quot;,
_ =&gt; &quot;Sorry, something is wrong! Please Try Again!&quot;,
};
write!(f, &quot;{}&quot;, err_msg)
}
}
impl fmt::Debug for AppError {
fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {
write!(
f,
&quot;AppError {{ code: {}, message: {} }}&quot;,
self.code, self.message
)
}
}
fn produce_error() -&gt; Result&lt;(), AppError&gt; {
Err(AppError {
code: 404,
message: String::from(&quot;Page not found&quot;),
})
}
fn main() {
match produce_error() {
Err(e) =&gt; eprintln!(&quot;{}&quot;, e), // 抱歉,未找到指定的页面!
_ =&gt; println!(&quot;No error&quot;),
}
eprintln!(&quot;{:?}&quot;, produce_error()); // Err(AppError { code: 404, message: Page not found })
eprintln!(&quot;{:#?}&quot;, produce_error());
// Err(
// AppError { code: 404, message: Page not found }
// )
}</code></pre></pre>
<p>在本例中,我们除了增加了错误码和消息外,还手动实现了 <code>Debug</code> 特征,原因在于,我们希望能自定义 <code>Debug</code> 的输出内容,而不是使用派生后系统提供的默认输出形式。</p>
<h4 id="错误转换-from-特征"><a class="header" href="#错误转换-from-特征">错误转换 <code>From</code> 特征</a></h4>
<p>标准库、三方库、本地库,各有各的精彩,各也有各的错误。那么问题就来了,我们该如何将其它的错误类型转换成自定义的错误类型?总不能神鬼牛魔,同台共舞吧。。</p>
<p>好在 Rust 为我们提供了 <code>std::convert::From</code> 特征:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub trait From&lt;T&gt;: Sized {
fn from(_: T) -&gt; Self;
}
<span class="boring">}</span></code></pre></pre>
<blockquote>
<p>事实上,该特征在之前的 <a href="https://course.rs/basic/result-error/result.html#%E4%BC%A0%E6%92%AD%E7%95%8C%E7%9A%84%E5%A4%A7%E6%98%8E%E6%98%9F-"><code>?</code> 操作符</a>章节中就有所介绍。</p>
<p>大家都使用过 <code>String::from</code> 函数吧?它可以通过 <code>&amp;str</code> 来创建一个 <code>String</code>,其实该函数就是 <code>From</code> 特征提供的</p>
</blockquote>
<p>下面一起来看看如何为自定义类型实现 <code>From</code> 特征:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fs::File;
use std::io;
#[derive(Debug)]
struct AppError {
kind: String, // 错误类型
message: String, // 错误信息
}
// 为 AppError 实现 std::convert::From 特征,由于 From 包含在 std::prelude 中,因此可以直接简化引入。
// 实现 From&lt;io::Error&gt; 意味着我们可以将 io::Error 错误转换成自定义的 AppError 错误
impl From&lt;io::Error&gt; for AppError {
fn from(error: io::Error) -&gt; Self {
AppError {
kind: String::from(&quot;io&quot;),
message: error.to_string(),
}
}
}
fn main() -&gt; Result&lt;(), AppError&gt; {
let _file = File::open(&quot;nonexistent_file.txt&quot;)?;
Ok(())
}
// --------------- 上述代码运行后输出 ---------------
Error: AppError { kind: &quot;io&quot;, message: &quot;No such file or directory (os error 2)&quot; }</code></pre></pre>
<p>上面的代码中除了实现 <code>From</code> 外,还有一点特别重要,那就是 <code>?</code> 可以将错误进行隐式的强制转换:<code>File::open</code> 返回的是 <code>std::io::Error</code> 我们并没有进行任何显式的转换,它就能自动变成 <code>AppError</code> ,这就是 <code>?</code> 的强大之处!</p>
<p>上面的例子只有一个标准库错误,再来看看多个不同的错误转换成 <code>AppError</code> 的实现:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fs::File;
use std::io::{self, Read};
use std::num;
#[derive(Debug)]
struct AppError {
kind: String,
message: String,
}
impl From&lt;io::Error&gt; for AppError {
fn from(error: io::Error) -&gt; Self {
AppError {
kind: String::from(&quot;io&quot;),
message: error.to_string(),
}
}
}
impl From&lt;num::ParseIntError&gt; for AppError {
fn from(error: num::ParseIntError) -&gt; Self {
AppError {
kind: String::from(&quot;parse&quot;),
message: error.to_string(),
}
}
}
fn main() -&gt; Result&lt;(), AppError&gt; {
let mut file = File::open(&quot;hello_world.txt&quot;)?;
let mut content = String::new();
file.read_to_string(&amp;mut content)?;
let _number: usize;
_number = content.parse()?;
Ok(())
}
// --------------- 上述代码运行后的可能输出 ---------------
// 01. 若 hello_world.txt 文件不存在
Error: AppError { kind: &quot;io&quot;, message: &quot;No such file or directory (os error 2)&quot; }
// 02. 若用户没有相关的权限访问 hello_world.txt
Error: AppError { kind: &quot;io&quot;, message: &quot;Permission denied (os error 13)&quot; }
// 03. 若 hello_world.txt 包含有非数字的内容,例如 Hello, world!
Error: AppError { kind: &quot;parse&quot;, message: &quot;invalid digit found in string&quot; }</code></pre></pre>
<h2 id="归一化不同的错误类型"><a class="header" href="#归一化不同的错误类型">归一化不同的错误类型</a></h2>
<p>至此,关于 Rust 的错误处理大家已经了若指掌了,下面再来看看一些实战中的问题。</p>
<p>在实际项目中,我们往往会为不同的错误定义不同的类型,这样做非常好,但是如果你要在一个函数中返回不同的错误呢?例如:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fs::read_to_string;
fn main() -&gt; Result&lt;(), std::io::Error&gt; {
let html = render()?;
println!(&quot;{}&quot;, html);
Ok(())
}
fn render() -&gt; Result&lt;String, std::io::Error&gt; {
let file = std::env::var(&quot;MARKDOWN&quot;)?;
let source = read_to_string(file)?;
Ok(source)
}</code></pre></pre>
<p>上面的代码会报错,原因在于 <code>render</code> 函数中的两个 <code>?</code> 返回的实际上是不同的错误:<code>env::var()</code> 返回的是 <code>std::env::VarError</code>,而 <code>read_to_string</code> 返回的是 <code>std::io::Error</code></p>
<p>为了满足 <code>render</code> 函数的签名,我们就需要将 <code>env::VarError</code><code>io::Error</code> 归一化为同一种错误类型。要实现这个目的有三种方式:</p>
<ul>
<li>使用特征对象 <code>Box&lt;dyn Error&gt;</code></li>
<li>自定义错误类型</li>
<li>使用 <code>thiserror</code></li>
</ul>
<p>下面依次来看看相关的解决方式。</p>
<h4 id="boxdyn-error"><a class="header" href="#boxdyn-error"><code>Box&lt;dyn Error&gt;</code></a></h4>
<p>大家还记得我们之前提到的 <code>std::error::Error</code> 特征吧,当时有说:自定义类型实现 <code>Debug + Display</code> 特征的主要原因就是为了能转换成 <code>Error</code> 的特征对象,而特征对象恰恰是在同一个地方使用不同类型的关键:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fs::read_to_string;
use std::error::Error;
fn main() -&gt; Result&lt;(), Box&lt;dyn Error&gt;&gt; {
let html = render()?;
println!(&quot;{}&quot;, html);
Ok(())
}
fn render() -&gt; Result&lt;String, Box&lt;dyn Error&gt;&gt; {
let file = std::env::var(&quot;MARKDOWN&quot;)?;
let source = read_to_string(file)?;
Ok(source)
}</code></pre></pre>
<p>这个方法很简单,在绝大多数场景中,性能也非常够用,但是有一个问题:<code>Result</code> 实际上不会限制错误的类型,也就是一个类型就算不实现 <code>Error</code> 特征,它依然可以在 <code>Result&lt;T, E&gt;</code> 中作为 <code>E</code> 来使用,此时这种特征对象的解决方案就无能为力了。</p>
<h4 id="自定义错误类型-1"><a class="header" href="#自定义错误类型-1">自定义错误类型</a></h4>
<p>与特征对象相比,自定义错误类型麻烦归麻烦,但是它非常灵活,因此也不具有上面的类似限制:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fs::read_to_string;
fn main() -&gt; Result&lt;(), MyError&gt; {
let html = render()?;
println!(&quot;{}&quot;, html);
Ok(())
}
fn render() -&gt; Result&lt;String, MyError&gt; {
let file = std::env::var(&quot;MARKDOWN&quot;)?;
let source = read_to_string(file)?;
Ok(source)
}
#[derive(Debug)]
enum MyError {
EnvironmentVariableNotFound,
IOError(std::io::Error),
}
impl From&lt;std::env::VarError&gt; for MyError {
fn from(_: std::env::VarError) -&gt; Self {
Self::EnvironmentVariableNotFound
}
}
impl From&lt;std::io::Error&gt; for MyError {
fn from(value: std::io::Error) -&gt; Self {
Self::IOError(value)
}
}
impl std::error::Error for MyError {}
impl std::fmt::Display for MyError {
fn fmt(&amp;self, f: &amp;mut std::fmt::Formatter&lt;'_&gt;) -&gt; std::fmt::Result {
match self {
MyError::EnvironmentVariableNotFound =&gt; write!(f, &quot;Environment variable not found&quot;),
MyError::IOError(err) =&gt; write!(f, &quot;IO Error: {}&quot;, err.to_string()),
}
}
}</code></pre></pre>
<p>上面代码中有一行值得注意:<code>impl std::error::Error for MyError {}</code> ,只有为自定义错误类型实现 <code>Error</code> 特征后,才能转换成相应的特征对象。</p>
<p>不得不说,真是啰嗦啊。因此在能用特征对象的时候,建议大家还是使用特征对象,无论如何,代码可读性还是很重要的!</p>
<p>上面的第二种方式灵活归灵活,啰嗦也是真啰嗦,好在 Rust 的社区为我们提供了 <code>thiserror</code> 解决方案,下面一起来看看该如何简化 Rust 中的错误处理。</p>
<h2 id="简化错误处理"><a class="header" href="#简化错误处理">简化错误处理</a></h2>
<p>对于开发者而言,错误处理是代码中打交道最多的部分之一,因此选择一把趁手的武器也很重要,它可以帮助我们节省大量的时间和精力,好钢应该用在代码逻辑而不是冗长的错误处理上。</p>
<h4 id="thiserror"><a class="header" href="#thiserror">thiserror</a></h4>
<p><a href="https://github.com/dtolnay/thiserror"><code>thiserror</code></a>可以帮助我们简化上面的第二种解决方案:</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fs::read_to_string;
fn main() -&gt; Result&lt;(), MyError&gt; {
let html = render()?;
println!(&quot;{}&quot;, html);
Ok(())
}
fn render() -&gt; Result&lt;String, MyError&gt; {
let file = std::env::var(&quot;MARKDOWN&quot;)?;
let source = read_to_string(file)?;
Ok(source)
}
#[derive(thiserror::Error, Debug)]
enum MyError {
#[error(&quot;Environment variable not found&quot;)]
EnvironmentVariableNotFound(#[from] std::env::VarError),
#[error(transparent)]
IOError(#[from] std::io::Error),
}</code></pre></pre>
<p>如上所示,只要简单写写注释,就可以实现错误处理了,惊不惊喜?</p>
<h4 id="error-chain"><a class="header" href="#error-chain">error-chain</a></h4>
<p><a href="https://github.com/rust-lang-deprecated/error-chain"><code>error-chain</code></a> 也是简单好用的库,可惜不再维护了,但是我觉得它依然可以在合适的地方大放光彩,值得大家去了解下。</p>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fs::read_to_string;
error_chain::error_chain! {
foreign_links {
EnvironmentVariableNotFound(::std::env::VarError);
IOError(::std::io::Error);
}
}
fn main() -&gt; Result&lt;()&gt; {
let html = render()?;
println!(&quot;{}&quot;, html);
Ok(())
}
fn render() -&gt; Result&lt;String&gt; {
let file = std::env::var(&quot;MARKDOWN&quot;)?;
let source = read_to_string(file)?;
Ok(source)
}</code></pre></pre>
<p>喏,简单吧?使用 <code>error-chain</code> 的宏你可以获得:<code>Error</code> 结构体,错误类型 <code>ErrorKind</code> 枚举 以及一个自定义的 <code>Result</code> 类型。</p>
<h4 id="anyhow"><a class="header" href="#anyhow">anyhow</a></h4>
<p><a href="https://github.com/dtolnay/anyhow"><code>anyhow</code></a><code>thiserror</code> 是同一个作者开发的,这里是作者关于 <code>anyhow</code><code>thiserror</code> 的原话:</p>
<blockquote>
<p>如果你想要设计自己的错误类型,同时给调用者提供具体的信息时,就使用 <code>thiserror</code>,例如当你在开发一个三方库代码时。如果你只想要简单,就使用 <code>anyhow</code>,例如在自己的应用服务中。</p>
</blockquote>
<pre><pre class="playground"><code class="language-rust edition2021">use std::fs::read_to_string;
use anyhow::Result;
fn main() -&gt; Result&lt;()&gt; {
let html = render()?;
println!(&quot;{}&quot;, html);
Ok(())
}
fn render() -&gt; Result&lt;String&gt; {
let file = std::env::var(&quot;MARKDOWN&quot;)?;
let source = read_to_string(file)?;
Ok(source)
}</code></pre></pre>
<p>关于如何选用 <code>thiserror</code><code>anyhow</code> 只需要遵循一个原则即可:<strong>是否关注自定义错误消息</strong>,关注则使用 <code>thiserror</code>(常见业务代码),否则使用 <code>anyhow</code>(编写第三方库代码)。</p>
<h2 id="总结"><a class="header" href="#总结">总结</a></h2>
<p>Rust 一个为人津津乐道的点就是强大、易用的错误处理,对于新手来说,这个机制可能会有些复杂,但是一旦体会到了其中的好处,你将跟我一样沉醉其中不能自拔。</p>
<div id="giscus-container"></div>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../advance/global-variable.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 prefetch" href="../advance/unsafe/intro.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="../advance/global-variable.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 prefetch" href="../advance/unsafe/intro.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>
window.playground_copyable = true;
</script>
<script src="../ace.js"></script>
<script src="../editor.js"></script>
<script src="../mode-rust.js"></script>
<script src="../theme-dawn.js"></script>
<script src="../theme-tomorrow_night.js"></script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<script type="text/javascript" charset="utf-8">
var pagePath = "advance/errors.md"
</script>
<!-- Custom JS scripts -->
<script src="../assets/custom.js"></script>
<script src="../assets/bigPicture.js"></script>
</div>
</body>
</html>