trpl-zh-cn/ch20-05-advanced-functions-and-closures.html

325 lines
36 KiB
HTML
Raw Normal View History

<!DOCTYPE HTML>
<html lang="en" class="light" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>高级函数与闭包 - Rust 程序设计语言 简体中文版</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="ferris.css">
<link rel="stylesheet" href="theme/2018-edition.css">
<link rel="stylesheet" href="theme/semantic-notes.css">
<link rel="stylesheet" href="theme/listing.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 expanded affix "><a href="title-page.html">Rust 程序设计语言</a></li><li class="chapter-item expanded affix "><a href="foreword.html">前言</a></li><li class="chapter-item expanded affix "><a href="ch00-00-introduction.html">简介</a></li><li class="chapter-item expanded "><a href="ch01-00-getting-started.html"><strong aria-hidden="true">1.</strong> 入门指南</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch01-01-installation.html"><strong aria-hidden="true">1.1.</strong> 安装</a></li><li class="chapter-item expanded "><a href="ch01-02-hello-world.html"><strong aria-hidden="true">1.2.</strong> Hello, World!</a></li><li class="chapter-item expanded "><a href="ch01-03-hello-cargo.html"><strong aria-hidden="true">1.3.</strong> Hello, Cargo!</a></li></ol></li><li class="chapter-item expanded "><a href="ch02-00-guessing-game-tutorial.html"><strong aria-hidden="true">2.</strong> 写个猜数字游戏</a></li><li class="chapter-item expanded "><a href="ch03-00-common-programming-concepts.html"><strong aria-hidden="true">3.</strong> 常见编程概念</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch03-01-variables-and-mutability.html"><strong aria-hidden="true">3.1.</strong> 变量与可变性</a></li><li class="chapter-item expanded "><a href="ch03-02-data-types.html"><strong aria-hidden="true">3.2.</strong> 数据类型</a></li><li class="chapter-item expanded "><a href="ch03-03-how-functions-work.html"><strong aria-hidden="true">3.3.</strong> 函数</a></li><li class="chapter-item expanded "><a href="ch03-04-comments.html"><strong aria-hidden="true">3.4.</strong> 注释</a></li><li class="chapter-item expanded "><a href="ch03-05-control-flow.html"><strong aria-hidden="true">3.5.</strong> 控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch04-00-understanding-ownership.html"><strong aria-hidden="true">4.</strong> 认识所有权</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch04-01-what-is-ownership.html"><strong aria-hidden="true">4.1.</strong> 什么是所有权?</a></li><li class="chapter-item expanded "><a href="ch04-02-references-and-borrowing.html"><strong aria-hidden="true">4.2.</strong> 引用与借用</a></li><li class="chapter-item expanded "><a href="ch04-03-slices.html"><strong aria-hidden="true">4.3.</strong> Slice 类型</a></li></ol></li><li class="chapter-item expanded "><a href="ch05-00-structs.html"><strong aria-hidden="true">5.</strong> 使用结构体组织相关联的数据</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch05-01-defining-structs.html"><strong aria-hidden="true">5.1.</strong> 结构体的定义和实例化</a></li><li class="chapter-item expanded "><a href="ch05-02-example-structs.html"><strong aria-hidden="true">5.2.</strong> 结构体示例程序</a></li><li class="chapter-item expanded "><a href="ch05-03-method-syntax.html"><strong aria-hidden="true">5.3.</strong> 方法语法</a></li></ol></li><li class="chapter-item expanded "><a href="ch06-00-enums.html"><strong aria-hidden="true">6.</strong> 枚举和模式匹配</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch06-01-defining-an-enum.html"><strong aria-hidden="true">6.1.</strong> 枚举的定义</a></li><li class="chapter-item expanded "><a href="ch06-02-match.html"><strong aria-hidden="true">6.2.</strong> match 控制流结构</a></li><li class="chapter-item expanded "><a href="ch06-03-if-let.html"><strong aria-hidden="true">6.3.</strong> if let 简洁控制流</a></li></ol></li><li class="chapter-item expanded "><a href="ch07-00-managing-growing-projects-with-packages-crates-and-modules.html"><strong aria-hidden="true">7.</strong> 使用包、Crate 和模块管理不断增长的项目</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="ch07-01-packages-and-crates.html"><strong aria-hidden="true">7.1.</strong> 包和 Crate</a></li><li class="chapter-item expanded "><a h
</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 程序设计语言 简体中文版</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/KaiserY/trpl-zh-cn/tree/main" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/KaiserY/trpl-zh-cn/edit/main/src/ch20-05-advanced-functions-and-closures.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">
<main>
<h2 id="高级函数与闭包"><a class="header" href="#高级函数与闭包">高级函数与闭包</a></h2>
<blockquote>
<p><a href="https://github.com/rust-lang/book/blob/main/src/ch20-05-advanced-functions-and-closures.md">ch20-05-advanced-functions-and-closures.md</a>
<br>
commit 21cf840842bdf768a798869f06373c96c1cc5122</p>
</blockquote>
<p>本部分将探索一些有关函数和闭包的高级功能,这包括函数指针以及返回值闭包。</p>
<h3 id="函数指针"><a class="header" href="#函数指针">函数指针</a></h3>
<p>我们讨论过了如何向函数传递闭包;也可以向函数传递常规函数!这个技术在我们希望传递已经定义的函数而不是重新定义闭包作为参数时很有用。函数满足类型 <code>fn</code>(小写的 f不要与闭包 trait 的 <code>Fn</code> 相混淆。<code>fn</code> 被称为 <strong>函数指针</strong><em>function pointer</em>)。通过函数指针允许我们使用函数作为另一个函数的参数。</p>
<p>指定参数为函数指针的语法类似于闭包,如示例 19-27 所示,这里定义了一个 <code>add_one</code> 函数将其参数加一。<code>do_twice</code> 函数获取两个参数:一个指向任何获取一个 <code>i32</code> 参数并返回一个 <code>i32</code> 的函数指针,和一个 <code>i32</code> 值。<code>do_twice</code> 函数传递 <code>arg</code> 参数调用 <code>f</code> 函数两次,接着将两次函数调用的结果相加。<code>main</code> 函数使用 <code>add_one</code><code>5</code> 作为参数调用 <code>do_twice</code></p>
<p><span class="filename">文件名src/main.rs</span></p>
<pre><pre class="playground"><code class="language-rust edition2021">fn add_one(x: i32) -&gt; i32 {
x + 1
}
fn do_twice(f: fn(i32) -&gt; i32, arg: i32) -&gt; i32 {
f(arg) + f(arg)
}
fn main() {
let answer = do_twice(add_one, 5);
println!("The answer is: {answer}");
}</code></pre></pre>
<p><span class="caption">示例 19-27: 使用 <code>fn</code> 类型接受函数指针作为参数</span></p>
<p>这会打印出 <code>The answer is: 12</code><code>do_twice</code> 中的 <code>f</code> 被指定为一个接受一个 <code>i32</code> 参数并返回 <code>i32</code><code>fn</code>。接着就可以在 <code>do_twice</code> 函数体中调用 <code>f</code>。在 <code>main</code> 中,可以将函数名 <code>add_one</code> 作为第一个参数传递给 <code>do_twice</code></p>
<p>不同于闭包,<code>fn</code> 是一个类型而不是一个 trait所以直接指定 <code>fn</code> 作为参数而不是声明一个带有 <code>Fn</code> 作为 trait bound 的泛型参数。</p>
<p>函数指针实现了所有三个闭包 trait<code>Fn</code><code>FnMut</code><code>FnOnce</code>),所以总是可以在调用期望闭包的函数时传递函数指针作为参数。倾向于编写使用泛型和闭包 trait 的函数,这样它就能接受函数或闭包作为参数。</p>
<p>一个只期望接受 <code>fn</code> 而不接受闭包的情况的例子是与不存在闭包的外部代码交互时C 语言的函数可以接受函数作为参数,但 C 语言没有闭包。</p>
<p>作为一个既可以使用内联定义的闭包又可以使用命名函数的例子,让我们看看一个 <code>map</code> 的应用。使用 <code>map</code> 函数将一个数字 vector 转换为一个字符串 vector就可以使用闭包比如这样</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">fn main() {
</span> let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec&lt;String&gt; =
list_of_numbers.iter().map(|i| i.to_string()).collect();
<span class="boring">}</span></code></pre></pre>
<p>或者可以将函数作为 <code>map</code> 的参数来代替闭包,像是这样:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">fn main() {
</span> let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec&lt;String&gt; =
list_of_numbers.iter().map(ToString::to_string).collect();
<span class="boring">}</span></code></pre></pre>
<p>注意这里必须使用 <a href="ch20-03-advanced-traits.html#%E9%AB%98%E7%BA%A7-trait">“高级 trait”</a> 部分讲到的完全限定语法,因为存在多个叫做 <code>to_string</code> 的函数;这里使用了定义于 <code>ToString</code> trait 的 <code>to_string</code> 函数,标准库为所有实现了 <code>Display</code> 的类型实现了这个 trait。</p>
<p>回忆一下第六章 <a href="ch06-01-defining-an-enum.html#%E6%9E%9A%E4%B8%BE%E5%80%BC">“枚举值”</a> 部分中定义的每一个枚举成员也变成了一个构造函数。我们可以使用这些构造函数作为实现了闭包 trait 的函数指针,这意味着可以指定构造函数作为接受闭包的方法的参数,如下:</p>
<pre><pre class="playground"><code class="language-rust edition2021"><span class="boring">fn main() {
</span> enum Status {
Value(u32),
Stop,
}
let list_of_statuses: Vec&lt;Status&gt; = (0u32..20).map(Status::Value).collect();
<span class="boring">}</span></code></pre></pre>
<p>这里创建了 <code>Status::Value</code> 实例,它通过 <code>map</code> 用范围的每一个 <code>u32</code> 值调用 <code>Status::Value</code> 的初始化函数。一些人倾向于函数风格,一些人喜欢闭包。这两种形式最终都会产生同样的代码,所以请使用对你来说更明白的形式吧。</p>
<h3 id="返回闭包"><a class="header" href="#返回闭包">返回闭包</a></h3>
<p>闭包表现为 trait这意味着不能直接返回闭包。对于大部分需要返回 trait 的情况,可以使用实现了期望返回的 trait 的具体类型来替代函数的返回值。但是这不能用于闭包,因为它们没有一个可返回的具体类型;例如不允许使用函数指针 <code>fn</code> 作为返回值类型。</p>
<p>这段代码尝试直接返回闭包,它并不能编译:</p>
<pre><code class="language-rust ignore does_not_compile">fn returns_closure() -&gt; dyn Fn(i32) -&gt; i32 {
|x| x + 1
}</code></pre>
<p>编译器给出的错误是:</p>
<pre><code class="language-console">$ cargo build
Compiling functions-example v0.1.0 (file:///projects/functions-example)
error[E0746]: return type cannot have an unboxed trait object
--&gt; src/lib.rs:1:25
|
1 | fn returns_closure() -&gt; dyn Fn(i32) -&gt; i32 {
| ^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
help: consider returning an `impl Trait` instead of a `dyn Trait`
|
1 | fn returns_closure() -&gt; impl Fn(i32) -&gt; i32 {
| ~~~~
help: alternatively, box the return type, and wrap all of the returned values in `Box::new`
|
1 ~ fn returns_closure() -&gt; Box&lt;dyn Fn(i32) -&gt; i32&gt; {
2 ~ Box::new(|x| x + 1)
|
For more information about this error, try `rustc --explain E0746`.
error: could not compile `functions-example` (lib) due to 1 previous error
</code></pre>
<p>错误又一次指向了 <code>Sized</code> traitRust 并不知道需要多少空间来储存闭包。不过我们在上一部分见过这种情况的解决办法:可以使用 trait 对象:</p>
<pre><code class="language-rust noplayground">fn returns_closure() -&gt; Box&lt;dyn Fn(i32) -&gt; i32&gt; {
Box::new(|x| x + 1)
}</code></pre>
<p>这段代码正好可以编译。关于 trait 对象的更多内容,请回顾第十八章的 <a href="ch18-02-trait-objects.html#%E9%A1%BE%E5%8F%8A%E4%B8%8D%E5%90%8C%E7%B1%BB%E5%9E%8B%E5%80%BC%E7%9A%84-trait-%E5%AF%B9%E8%B1%A1">顾及不同类型值的 trait 对象”</a> 部分。</p>
<p>接下来让我们学习宏!</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="ch20-04-advanced-types.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="ch20-06-macros.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="ch20-04-advanced-types.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="ch20-06-macros.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="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>
<!-- Custom JS scripts -->
<script src="ferris.js"></script>
</div>
</body>
</html>