mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
351 lines
32 KiB
HTML
351 lines
32 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>trait:定义共享的行为 - Rust 程序设计语言 简体中文版</title>
|
||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||
<meta name="description" content="Rust 程序设计语言 简体中文版">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
|
||
<base href="">
|
||
|
||
<link rel="stylesheet" href="book.css">
|
||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>
|
||
|
||
<link rel="shortcut icon" href="favicon.png">
|
||
|
||
<!-- Font Awesome -->
|
||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
|
||
|
||
<link rel="stylesheet" href="highlight.css">
|
||
<link rel="stylesheet" href="tomorrow-night.css">
|
||
|
||
<!-- MathJax -->
|
||
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
|
||
|
||
<!-- Fetch JQuery from CDN but have a local fallback -->
|
||
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
||
<script>
|
||
if (typeof jQuery == 'undefined') {
|
||
document.write(unescape("%3Cscript src='jquery.js'%3E%3C/script%3E"));
|
||
}
|
||
</script>
|
||
</head>
|
||
<body class="light">
|
||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
<script type="text/javascript">
|
||
var theme = localStorage.getItem('theme');
|
||
if (theme == null) { theme = 'light'; }
|
||
$('body').removeClass().addClass(theme);
|
||
</script>
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script type="text/javascript">
|
||
var sidebar = localStorage.getItem('sidebar');
|
||
if (sidebar === "hidden") { $("html").addClass("sidebar-hidden") }
|
||
else if (sidebar === "visible") { $("html").addClass("sidebar-visible") }
|
||
</script>
|
||
|
||
<div id="sidebar" class="sidebar">
|
||
<ul class="chapter"><li><a href="ch01-00-introduction.html"><strong>1.</strong> 介绍</a></li><li><ul class="section"><li><a href="ch01-01-installation.html"><strong>1.1.</strong> 安装</a></li><li><a href="ch01-02-hello-world.html"><strong>1.2.</strong> Hello, World!</a></li></ul></li><li><a href="ch02-00-guessing-game-tutorial.html"><strong>2.</strong> 猜猜看教程</a></li><li><a href="ch03-00-common-programming-concepts.html"><strong>3.</strong> 通用编程概念</a></li><li><ul class="section"><li><a href="ch03-01-variables-and-mutability.html"><strong>3.1.</strong> 变量和可变性</a></li><li><a href="ch03-02-data-types.html"><strong>3.2.</strong> 数据类型</a></li><li><a href="ch03-03-how-functions-work.html"><strong>3.3.</strong> 函数如何工作</a></li><li><a href="ch03-04-comments.html"><strong>3.4.</strong> 注释</a></li><li><a href="ch03-05-control-flow.html"><strong>3.5.</strong> 控制流</a></li></ul></li><li><a href="ch04-00-understanding-ownership.html"><strong>4.</strong> 认识所有权</a></li><li><ul class="section"><li><a href="ch04-01-what-is-ownership.html"><strong>4.1.</strong> 什么是所有权</a></li><li><a href="ch04-02-references-and-borrowing.html"><strong>4.2.</strong> 引用 & 借用</a></li><li><a href="ch04-03-slices.html"><strong>4.3.</strong> Slices</a></li></ul></li><li><a href="ch05-00-structs.html"><strong>5.</strong> 结构体</a></li><li><ul class="section"><li><a href="ch05-01-method-syntax.html"><strong>5.1.</strong> 方法语法</a></li></ul></li><li><a href="ch06-00-enums.html"><strong>6.</strong> 枚举和模式匹配</a></li><li><ul class="section"><li><a href="ch06-01-defining-an-enum.html"><strong>6.1.</strong> 定义枚举</a></li><li><a href="ch06-02-match.html"><strong>6.2.</strong> <code>match</code>控制流运算符</a></li><li><a href="ch06-03-if-let.html"><strong>6.3.</strong> <code>if let</code>简单控制流</a></li></ul></li><li><a href="ch07-00-modules.html"><strong>7.</strong> 模块</a></li><li><ul class="section"><li><a href="ch07-01-mod-and-the-filesystem.html"><strong>7.1.</strong> <code>mod</code>和文件系统</a></li><li><a href="ch07-02-controlling-visibility-with-pub.html"><strong>7.2.</strong> 使用<code>pub</code>控制可见性</a></li><li><a href="ch07-03-importing-names-with-use.html"><strong>7.3.</strong> 使用<code>use</code>导入命名</a></li></ul></li><li><a href="ch08-00-common-collections.html"><strong>8.</strong> 通用集合类型</a></li><li><ul class="section"><li><a href="ch08-01-vectors.html"><strong>8.1.</strong> vector</a></li><li><a href="ch08-02-strings.html"><strong>8.2.</strong> 字符串</a></li><li><a href="ch08-03-hash-maps.html"><strong>8.3.</strong> 哈希 map</a></li></ul></li><li><a href="ch09-00-error-handling.html"><strong>9.</strong> 错误处理</a></li><li><ul class="section"><li><a href="ch09-01-unrecoverable-errors-with-panic.html"><strong>9.1.</strong> <code>panic!</code>与不可恢复的错误</a></li><li><a href="ch09-02-recoverable-errors-with-result.html"><strong>9.2.</strong> <code>Result</code>与可恢复的错误</a></li><li><a href="ch09-03-to-panic-or-not-to-panic.html"><strong>9.3.</strong> <code>panic!</code>还是不<code>panic!</code></a></li></ul></li><li><a href="ch10-00-generics.html"><strong>10.</strong> 泛型、trait 和生命周期</a></li><li><ul class="section"><li><a href="ch10-01-syntax.html"><strong>10.1.</strong> 泛型数据类型</a></li><li><a href="ch10-02-traits.html" class="active"><strong>10.2.</strong> trait:定义共享的行为</a></li><li><a href="ch10-03-lifetime-syntax.html"><strong>10.3.</strong> 生命周期与引用有效性</a></li></ul></li><li><a href="ch11-00-testing.html"><strong>11.</strong> 测试</a></li><li><ul class="section"><li><a href="ch11-01-writing-tests.html"><strong>11.1.</strong> 编写测试</a></li><li><a href="ch11-02-running-tests.html"><strong>11.2.</strong> 运行测试</a></li><li><a href="ch11-03-test-organization.html"><strong>11.3.</strong> 测试的组织结构</a></li></ul></li><li><a href="ch12-00-an-io-project.html"><strong>12.</strong> 一个 I/O 项目</a></li><li><ul class="section"><li><a href="ch12-01-accepting-command-line-arguments.html"><strong>12.1.</strong> 接受命令行参数</a></li><li><a href="ch12-02-reading-a-file.html"><strong>12.2.</strong> 读取文件</a></li><li><a href="ch12-03-improving-error-handling-and-modularity.html"><strong>12.3.</strong> 增强错误处理和模块化</a></li><li><a href="ch12-04-testing-the-librarys-functionality.html"><strong>12.4.</strong> 测试库的功能</a></li><li><a href="ch12-05-working-with-environment-variables.html"><strong>12.5.</strong> 处理环境变量</a></li><li><a href="ch12-06-writing-to-stderr-instead-of-stdout.html"><strong>12.6.</strong> 输出到<code>stderr</code>而不是<code>stdout</code></a></li></ul></li><li><a href="ch13-00-functional-features.html"><strong>13.</strong> Rust 中的函数式语言功能</a></li><li><ul class="section"><li><a href="ch13-01-closures.html"><strong>13.1.</strong> 闭包</a></li><li><a href="ch13-02-iterators.html"><strong>13.2.</strong> 迭代器</a></li><li><a href="ch13-03-improving-our-io-project.html"><strong>13.3.</strong> 改进 I/O 项目</a></li><li><a href="ch13-04-performance.html"><strong>13.4.</strong> 性能</a></li></ul></li><li><a href="ch14-00-more-about-cargo.html"><strong>14.</strong> 更多关于 Cargo 和 Crates.io</a></li><li><ul class="section"><li><a href="ch14-01-release-profiles.html"><strong>14.1.</strong> 发布配置</a></li><li><a href="ch14-02-publishing-to-crates-io.html"><strong>14.2.</strong> 将 crate 发布到 Crates.io</a></li><li><a href="ch14-03-cargo-workspaces.html"><strong>14.3.</strong> Cargo 工作空间</a></li><li><a href="ch14-04-installing-binaries.html"><strong>14.4.</strong> 使用<code>cargo install</code>从 Crates.io 安装文件</a></li><li><a href="ch14-05-extending-cargo.html"><strong>14.5.</strong> Cargo 自定义扩展命令</a></li></ul></li><li><a href="ch15-00-smart-pointers.html"><strong>15.</strong> 智能指针</a></li><li><ul class="section"><li><a href="ch15-01-box.html"><strong>15.1.</strong> <code>Box<T></code>用于已知大小的堆上数据</a></li><li><a href="ch15-02-deref.html"><strong>15.2.</strong> <code>Deref</code> Trait 允许通过引用访问数据</a></li><li><a href="ch15-03-drop.html"><strong>15.3.</strong> <code>Drop</code> Trait 运行清理代码</a></li><li><a href="ch15-04-rc.html"><strong>15.4.</strong> <code>Rc<T></code> 引用计数智能指针</a></li><li><a href="ch15-05-interior-mutability.html"><strong>15.5.</strong> <code>RefCell<T></code>和内部可变性模式</a></li><li><a href="ch15-06-reference-cycles.html"><strong>15.6.</strong> 引用循环和内存泄漏是安全的</a></li></ul></li><li><a href="ch16-00-concurrency.html"><strong>16.</strong> 无畏并发</a></li><li><ul class="section"><li><a href="ch16-01-threads.html"><strong>16.1.</strong> 线程</a></li><li><a href="ch16-02-message-passing.html"><strong>16.2.</strong> 消息传递</a></li><li><a href="ch16-03-shared-state.html"><strong>16.3.</strong> 共享状态</a></li><li><a href="ch16-04-extensible-concurrency-sync-and-send.html"><strong>16.4.</strong> 可扩展的并发:<code>Sync</code>和<code>Send</code></a></li></ul></li><li><a href="ch17-00-oop.html"><strong>17.</strong> 面向对象</a></li><li><ul class="section"><li><a href="ch17-01-what-is-oo.html"><strong>17.1.</strong> 什么是面向对象</a></li><li><a href="ch17-02-trait-objects.html"><strong>17.2.</strong> trait对象</a></li></ul></li></ul>
|
||
</div>
|
||
|
||
<div id="page-wrapper" class="page-wrapper">
|
||
|
||
<div class="page">
|
||
<div id="menu-bar" class="menu-bar">
|
||
<div class="left-buttons">
|
||
<i id="sidebar-toggle" class="fa fa-bars"></i>
|
||
<i id="theme-toggle" class="fa fa-paint-brush"></i>
|
||
</div>
|
||
|
||
<h1 class="menu-title">Rust 程序设计语言 简体中文版</h1>
|
||
|
||
<div class="right-buttons">
|
||
<i id="print-button" class="fa fa-print" title="Print this book"></i>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="content" class="content">
|
||
<a class="header" href="#trait定义共享的行为" name="trait定义共享的行为"><h2>trait:定义共享的行为</h2></a>
|
||
<blockquote>
|
||
<p><a href="https://github.com/rust-lang/book/blob/master/second-edition/src/ch10-02-traits.md">ch10-02-traits.md</a>
|
||
<br>
|
||
commit e5a987f5da3fba24e55f5c7102ec63f9dc3bc360</p>
|
||
</blockquote>
|
||
<p>trait 允许我们进行另一种抽象:他们让我们可以抽象类型所通用的行为。<em>trait</em> 告诉 Rust 编译器某个特定类型拥有可能与其他类型共享的功能。在使用泛型类型参数的场景中,可以使用 <em>trait bounds</em> 在编译时指定泛型可以是任何实现了某个 trait 的类型,并由此在这个场景下拥有我们希望的功能。</p>
|
||
<blockquote>
|
||
<p>注意:<em>trait</em> 类似于其他语言中的常被称为<strong>接口</strong>(<em>interfaces</em>)的功能,虽然有一些不同。</p>
|
||
</blockquote>
|
||
<a class="header" href="#定义-trait" name="定义-trait"><h3>定义 trait</h3></a>
|
||
<p>一个类型的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。</p>
|
||
<p>例如,这里有多个存放了不同类型和属性文本的结构体:结构体<code>NewsArticle</code>用于存放发生于世界各地的新闻故事,而结构体<code>Tweet</code>最多只能存放 140 个字符的内容,以及像是否转推或是否是对推友的回复这样的元数据。</p>
|
||
<p>我们想要创建一个多媒体聚合库用来显示可能储存在<code>NewsArticle</code>或<code>Tweet</code>实例中的数据的总结。每一个结构体都需要的行为是他们是能够被总结的,这样的话就可以调用实例的<code>summary</code>方法来请求总结。列表 10-11 中展示了一个表现这个概念的<code>Summarizable</code> trait 的定义:</p>
|
||
<p><span class="filename">Filename: lib.rs</span></p>
|
||
<pre><code class="language-rust">pub trait Summarizable {
|
||
fn summary(&self) -> String;
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">Listing 10-11: Definition of a <code>Summarizable</code> trait that
|
||
consists of the behavior provided by a <code>summary</code> method</span></p>
|
||
<p>使用<code>trait</code>关键字来定义一个 trait,后面是 trait 的名字,在这个例子中是<code>Summarizable</code>。在大括号中声明描述实现这个 trait 的类型所需要的行为的方法签名,在这个例子中是是<code>fn summary(&self) -> String</code>。在方法签名后跟分号而不是在大括号中提供其实现。接着每一个实现这个 trait 的类型都需要提供其自定义行为的方法体,编译器也会确保任何实现<code>Summarizable</code> trait 的类型都拥有与这个签名的定义完全一致的<code>summary</code>方法。</p>
|
||
<p>trait 体中可以有多个方法,一行一个方法签名且都以分号结尾。</p>
|
||
<a class="header" href="#为类型实现-trait" name="为类型实现-trait"><h3>为类型实现 trait</h3></a>
|
||
<p>现在我们定义了<code>Summarizable</code> trait,接着就可以在多媒体聚合库中需要拥有这个行为的类型上实现它了。列表 10-12 中展示了<code>NewsArticle</code>结构体上<code>Summarizable</code> trait 的一个实现,它使用标题、作者和创建的位置作为<code>summary</code>的返回值。对于<code>Tweet</code>结构体,我们选择将<code>summary</code>定义为用户名后跟推文的全部文本作为返回值,并假设推文内容已经被限制为 140 字符以内。</p>
|
||
<p><span class="filename">Filename: lib.rs</span></p>
|
||
<pre><code class="language-rust"># pub trait Summarizable {
|
||
# fn summary(&self) -> String;
|
||
# }
|
||
#
|
||
pub struct NewsArticle {
|
||
pub headline: String,
|
||
pub location: String,
|
||
pub author: String,
|
||
pub content: String,
|
||
}
|
||
|
||
impl Summarizable for NewsArticle {
|
||
fn summary(&self) -> String {
|
||
format!("{}, by {} ({})", self.headline, self.author, self.location)
|
||
}
|
||
}
|
||
|
||
pub struct Tweet {
|
||
pub username: String,
|
||
pub content: String,
|
||
pub reply: bool,
|
||
pub retweet: bool,
|
||
}
|
||
|
||
impl Summarizable for Tweet {
|
||
fn summary(&self) -> String {
|
||
format!("{}: {}", self.username, self.content)
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">Listing 10-12: Implementing the <code>Summarizable</code> trait on
|
||
the <code>NewsArticle</code> and <code>Tweet</code> types</span></p>
|
||
<p>在类型上实现 trait 类似与实现与 trait 无关的方法。区别在于<code>impl</code>关键字之后,我们提供需要实现 trait 的名称,接着是<code>for</code>和需要实现 trait 的类型的名称。在<code>impl</code>块中,使用 trait 定义中的方法签名,不过不再后跟分号,而是需要在大括号中编写函数体来为特定类型实现 trait 方法所拥有的行为。</p>
|
||
<p>一旦实现了 trait,我们就可以用与<code>NewsArticle</code>和<code>Tweet</code>实例的非 trait 方法一样的方式调用 trait 方法了:</p>
|
||
<pre><code class="language-rust,ignore">let tweet = Tweet {
|
||
username: String::from("horse_ebooks"),
|
||
content: String::from("of course, as you probably already know, people"),
|
||
reply: false,
|
||
retweet: false,
|
||
};
|
||
|
||
println!("1 new tweet: {}", tweet.summary());
|
||
</code></pre>
|
||
<p>这会打印出<code>1 new tweet: horse_ebooks: of course, as you probably already know, people</code>。</p>
|
||
<p>注意因为列表 10-12 中我们在相同的<code>lib.rs</code>里定义了<code>Summarizable</code> trait 和<code>NewsArticle</code>与<code>Tweet</code>类型,所以他们是位于同一作用域的。如果这个<code>lib.rs</code>是对应<code>aggregator</code> crate 的,而别人想要利用我们 crate 的功能外加为其<code>WeatherForecast</code>结构体实现<code>Summarizable</code> trait,在实现<code>Summarizable</code> trait 之前他们首先就需要将其导入其作用域中,如列表 10-13 所示:</p>
|
||
<p><span class="filename">Filename: lib.rs</span></p>
|
||
<pre><code class="language-rust,ignore">extern crate aggregator;
|
||
|
||
use aggregator::Summarizable;
|
||
|
||
struct WeatherForecast {
|
||
high_temp: f64,
|
||
low_temp: f64,
|
||
chance_of_precipitation: f64,
|
||
}
|
||
|
||
impl Summarizable for WeatherForecast {
|
||
fn summary(&self) -> String {
|
||
format!("The high will be {}, and the low will be {}. The chance of
|
||
precipitation is {}%.", self.high_temp, self.low_temp,
|
||
self.chance_of_precipitation)
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">Listing 10-13: Bringing the <code>Summarizable</code> trait from our
|
||
<code>aggregator</code> crate into scope in another crate</span></p>
|
||
<p>另外这段代码假设<code>Summarizable</code>是一个公有 trait,这是因为列表 10-11 中<code>trait</code>之前使用了<code>pub</code>关键字。</p>
|
||
<p>trait 实现的一个需要注意的限制是:只能在 trait 或对应类型位于我们 crate 本地的时候为其实现 trait。换句话说,不允许对外部类型实现外部 trait。例如,不能<code>Vec</code>上实现<code>Display</code> trait,因为<code>Display</code>和<code>Vec</code>都定义于标准库中。允许在像<code>Tweet</code>这样作为我们<code>aggregator</code>crate 部分功能的自定义类型上实现标准库中的 trait <code>Display</code>。也允许在<code>aggregator</code>crate中为<code>Vec</code>实现<code>Summarizable</code>,因为<code>Summarizable</code>定义与此。这个限制是我们称为 <em>orphan rule</em> 的一部分,如果你感兴趣的可以在类型理论中找到它。简单来说,它被称为 orphan rule 是因为其父类型不存在。没有这条规则的话,两个 crate 可以分别对相同类型是实现相同的 trait,因而这两个实现会相互冲突:Rust 将无从得知应该使用哪一个。因为 Rust 强制执行 orphan rule,其他人编写的代码不会破坏你代码,反之亦是如此。</p>
|
||
<a class="header" href="#默认实现" name="默认实现"><h3>默认实现</h3></a>
|
||
<p>有时为 trait 中的某些或全部提供默认的行为,而不是在每个类型的每个实现中都定义自己的行为是很有用的。这样当为某个特定类型实现 trait 时,可以选择保留或重载每个方法的默认行为。</p>
|
||
<p>列表 10-14 中展示了如何为<code>Summarize</code> trait 的<code>summary</code>方法指定一个默认的字符串值,而不是像列表 10-11 中那样只是定义方法签名:</p>
|
||
<p><span class="filename">Filename: lib.rs</span></p>
|
||
<pre><code class="language-rust">pub trait Summarizable {
|
||
fn summary(&self) -> String {
|
||
String::from("(Read more...)")
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">Listing 10-14: Definition of a <code>Summarizable</code> trait with
|
||
a default implementation of the <code>summary</code> method</span></p>
|
||
<p>如果想要对<code>NewsArticle</code>实例使用这个默认实现,而不是像列表 10-12 中那样定义一个自己的实现,则可以指定一个空的<code>impl</code>块:</p>
|
||
<pre><code class="language-rust,ignore">impl Summarizable for NewsArticle {}
|
||
</code></pre>
|
||
<p>即便选择不再直接为<code>NewsArticle</code>定义<code>summary</code>方法了,因为<code>summary</code>方法有一个默认实现而且<code>NewsArticle</code>被指定为实现了<code>Summarizable</code> trait,我们仍然可以对<code>NewsArticle</code>的实例调用<code>summary</code>方法:</p>
|
||
<pre><code class="language-rust,ignore">let article = NewsArticle {
|
||
headline: String::from("Penguins win the Stanley Cup Championship!"),
|
||
location: String::from("Pittsburgh, PA, USA"),
|
||
author: String::from("Iceburgh"),
|
||
content: String::from("The Pittsburgh Penguins once again are the best
|
||
hockey team in the NHL."),
|
||
};
|
||
|
||
println!("New article available! {}", article.summary());
|
||
</code></pre>
|
||
<p>这段代码会打印<code>New article available! (Read more...)</code>。</p>
|
||
<p>将<code>Summarizable</code> trait 改变为拥有默认<code>summary</code>实现并不要求对列表 10-12 中的<code>Tweet</code>和列表 10-13 中的<code>WeatherForecast</code>对<code>Summarizable</code>的实现做任何改变:重载一个默认实现的语法与实现没有默认实现的 trait 方法时完全一样的。</p>
|
||
<p>默认实现允许调用相同 trait 中的其他方法,哪怕这些方法没有默认实现。通过这种方法,trait 可以实现很多有用的功能而只需实现一小部分特定内容。我们可以选择让<code>Summarizable</code> trait 也拥有一个要求实现的<code>author_summary</code>方法,接着<code>summary</code>方法则提供默认实现并调用<code>author_summary</code>方法:</p>
|
||
<pre><code class="language-rust">pub trait Summarizable {
|
||
fn author_summary(&self) -> String;
|
||
|
||
fn summary(&self) -> String {
|
||
format!("(Read more from {}...)", self.author_summary())
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>为了使用这个版本的<code>Summarizable</code>,只需在实现 trait 时定义<code>author_summary</code>即可:</p>
|
||
<pre><code class="language-rust,ignore">impl Summarizable for Tweet {
|
||
fn author_summary(&self) -> String {
|
||
format!("@{}", self.username)
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>一旦定义了<code>author_summary</code>,我们就可以对<code>Tweet</code>结构体的实例调用<code>summary</code>了,而<code>summary</code>的默认实现会调用我们提供的<code>author_summary</code>定义。</p>
|
||
<pre><code class="language-rust,ignore">let tweet = Tweet {
|
||
username: String::from("horse_ebooks"),
|
||
content: String::from("of course, as you probably already know, people"),
|
||
reply: false,
|
||
retweet: false,
|
||
};
|
||
|
||
println!("1 new tweet: {}", tweet.summary());
|
||
</code></pre>
|
||
<p>这会打印出<code>1 new tweet: (Read more from @horse_ebooks...)</code>。</p>
|
||
<p>注意在重载过的实现中调用默认实现是不可能的。</p>
|
||
<a class="header" href="#trait-bounds" name="trait-bounds"><h3>trait bounds</h3></a>
|
||
<p>现在我们定义了 trait 并在类型上实现了这些 trait,也可以对泛型类型参数使用 trait。我们可以限制泛型不再适用于任何类型,编译器会确保其被限制为那些实现了特定 trait 的类型,由此泛型就会拥有我们希望其类型所拥有的功能。这被称为指定泛型的 <em>trait bounds</em>。</p>
|
||
<p>例如在列表 10-12 中为<code>NewsArticle</code>和<code>Tweet</code>类型实现了<code>Summarizable</code> trait。我们可以定义一个函数<code>notify</code>来调用<code>summary</code>方法,它拥有一个泛型类型<code>T</code>的参数<code>item</code>。为了能够在<code>item</code>上调用<code>summary</code>而不出现错误,我们可以在<code>T</code>上使用 trait bounds 来指定<code>item</code>必须是实现了<code>Summarizable</code> trait 的类型:</p>
|
||
<pre><code class="language-rust,ignore">pub fn notify<T: Summarizable>(item: T) {
|
||
println!("Breaking news! {}", item.summary());
|
||
}
|
||
</code></pre>
|
||
<p>trait bounds 连同泛型类型参数声明一同出现,位于尖括号中的冒号后面。由于<code>T</code>上的 trait bounds,我们可以传递任何<code>NewsArticle</code>或<code>Tweet</code>的实例来调用<code>notify</code>函数。列表 10-13 中使用我们<code>aggregator</code> crate 的外部代码也可以传递一个<code>WeatherForecast</code>的实例来调用<code>notify</code>函数,因为<code>WeatherForecast</code>同样也实现了<code>Summarizable</code>。使用任何其他类型,比如<code>String</code>或<code>i32</code>,来调用<code>notify</code>的代码将不能编译,因为这些类型没有实现<code>Summarizable</code>。</p>
|
||
<p>可以通过<code>+</code>来为泛型指定多个 trait bounds。如果我们需要能够在函数中使用<code>T</code>类型的显示格式的同时也能使用<code>summary</code>方法,则可以使用 trait bounds <code>T: Summarizable + Display</code>。这意味着<code>T</code>可以是任何实现了<code>Summarizable</code>和<code>Display</code>的类型。</p>
|
||
<p>对于拥有多个泛型类型参数的函数,每一个泛型都可以有其自己的 trait bounds。在函数名和参数列表之间的尖括号中指定很多的 trait bound 信息将是难以阅读的,所以有另外一个指定 trait bounds 的语法,它将其移动到函数签名后的<code>where</code>从句中。所以相比这样写:</p>
|
||
<pre><code class="language-rust,ignore">fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {
|
||
</code></pre>
|
||
<p>我们也可以使用<code>where</code>从句:</p>
|
||
<pre><code class="language-rust,ignore">fn some_function<T, U>(t: T, u: U) -> i32
|
||
where T: Display + Clone,
|
||
U: Clone + Debug
|
||
{
|
||
</code></pre>
|
||
<p>这就显得不那么杂乱,同时也使这个函数看起来更像没有很多 trait bounds 的函数。这时函数名、参数列表和返回值类型都离得很近。</p>
|
||
<a class="header" href="#使用-trait-bounds-来修复largest函数" name="使用-trait-bounds-来修复largest函数"><h3>使用 trait bounds 来修复<code>largest</code>函数</h3></a>
|
||
<p>所以任何想要对泛型使用 trait 定义的行为的时候,都需要在泛型参数类型上指定 trait bounds。现在我们就可以修复列表 10-5 中那个使用泛型类型参数的<code>largest</code>函数定义了!当我们将其放置不管的时候,它会出现这个错误:</p>
|
||
<pre><code>error[E0369]: binary operation `>` cannot be applied to type `T`
|
||
|
|
||
5 | if item > largest {
|
||
| ^^^^
|
||
|
|
||
note: an implementation of `std::cmp::PartialOrd` might be missing for `T`
|
||
</code></pre>
|
||
<p>在<code>largest</code>函数体中我们想要使用大于运算符比较两个<code>T</code>类型的值。这个运算符被定义为标准库中 trait <code>std::cmp::PartialOrd</code> 的一个默认方法。所以为了能够使用大于运算符,需要在<code>T</code>的 trait bounds 中指定<code>PartialOrd</code>,这样<code>largest</code>函数可以用于任何可以比较大小的类型的 slice。因为<code>PartialOrd</code>位于 prelude 中所以并不需要手动将其引入作用域。</p>
|
||
<pre><code class="language-rust,ignore">fn largest<T: PartialOrd>(list: &[T]) -> T {
|
||
</code></pre>
|
||
<p>但是如果编译代码的话,会出现不同的错误:</p>
|
||
<pre><code class="language-text">error[E0508]: cannot move out of type `[T]`, a non-copy array
|
||
--> src/main.rs:4:23
|
||
|
|
||
4 | let mut largest = list[0];
|
||
| ----------- ^^^^^^^ cannot move out of here
|
||
| |
|
||
| hint: to prevent move, use `ref largest` or `ref mut largest`
|
||
|
||
error[E0507]: cannot move out of borrowed content
|
||
--> src/main.rs:6:9
|
||
|
|
||
6 | for &item in list.iter() {
|
||
| ^----
|
||
| ||
|
||
| |hint: to prevent move, use `ref item` or `ref mut item`
|
||
| cannot move out of borrowed content
|
||
</code></pre>
|
||
<p>错误的核心是<code>cannot move out of type [T], a non-copy array</code>,对于非泛型版本的<code>largest</code>函数,我们只尝试了寻找最大的<code>i32</code>和<code>char</code>。正如第四章讨论过的,像<code>i32</code>和<code>char</code>这样的类型是已知大小的并可以储存在栈上,所以他们实现了<code>Copy</code> trait。当我们将<code>largest</code>函数改成使用泛型后,现在<code>list</code>参数的类型就有可能是没有实现<code>Copy</code> trait 的,这意味着我们可能不能将<code>list[0]</code>的值移动到<code>largest</code>变量中。</p>
|
||
<p>如果只想对实现了<code>Copy</code>的类型调用这些代码,可以在<code>T</code>的 trait bounds 中增加<code>Copy</code>!列表 10-15 中展示了一个可以编译的泛型版本的<code>largest</code>函数的完整代码,只要传递给<code>largest</code>的 slice 值的类型实现了<code>PartialOrd</code>和<code>Copy</code>这两个 trait,例如<code>i32</code>和<code>char</code>:</p>
|
||
<p><span class="filename">Filename: src/main.rs</span></p>
|
||
<pre><code class="language-rust">use std::cmp::PartialOrd;
|
||
|
||
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
|
||
let mut largest = list[0];
|
||
|
||
for &item in list.iter() {
|
||
if item > largest {
|
||
largest = item;
|
||
}
|
||
}
|
||
|
||
largest
|
||
}
|
||
|
||
fn main() {
|
||
let numbers = vec![34, 50, 25, 100, 65];
|
||
|
||
let result = largest(&numbers);
|
||
println!("The largest number is {}", result);
|
||
|
||
let chars = vec!['y', 'm', 'a', 'q'];
|
||
|
||
let result = largest(&chars);
|
||
println!("The largest char is {}", result);
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">Listing 10-15: A working definition of the <code>largest</code>
|
||
function that works on any generic type that implements the <code>PartialOrd</code> and
|
||
<code>Copy</code> traits</span></p>
|
||
<p>如果并不希望限制<code>largest</code>函数只能用于实现了<code>Copy</code> trait 的类型,我们可以在<code>T</code>的 trait bounds 中指定<code>Clone</code>而不是<code>Copy</code>,并克隆 slice 的每一个值使得<code>largest</code>函数拥有其所有权。但是使用<code>clone</code>函数潜在意味着更多的堆分配,而且堆分配在涉及大量数据时可能会相当缓慢。另一种<code>largest</code>的实现方式是返回 slice 中一个<code>T</code>值的引用。如果我们将函数返回值从<code>T</code>改为<code>&T</code>并改变函数体使其能够返回一个引用,我们将不需要任何<code>Clone</code>或<code>Copy</code>的 trait bounds 而且也不会有任何的堆分配。尝试自己实现这种替代解决方式吧!</p>
|
||
<p>trait 和 trait bounds 让我们使用泛型类型参数来减少重复,并仍然能够向编译器明确指定泛型类型需要拥有哪些行为。因为我们向编译器提供了 trait bounds 信息,它就可以检查代码中所用到的具体类型是否提供了正确的行为。在动态类型语言中,如果我们尝试调用一个类型并没有实现的方法,会在运行时出现错误。Rust 将这些错误移动到了编译时,甚至在代码能够运行之前就强迫我们修复错误。另外,我们也无需编写运行时检查行为的代码,因为在编译时就已经检查过了,这样相比其他那些不愿放弃泛型灵活性的语言有更好的性能。</p>
|
||
<p>这里还有一种泛型,我们一直在使用它甚至都没有察觉它的存在,这就是<strong>生命周期</strong>(<em>lifetimes</em>)。不同于其他泛型帮助我们确保类型拥有期望的行为,生命周期则有助于确保引用在我们需要他们的时候一直有效。让我们学习生命周期是如何做到这些的。</p>
|
||
|
||
</div>
|
||
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
<a href="ch10-01-syntax.html" class="mobile-nav-chapters previous">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
|
||
|
||
<a href="ch10-03-lifetime-syntax.html" class="mobile-nav-chapters next">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
<a href="ch10-01-syntax.html" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
|
||
|
||
<a href="ch10-03-lifetime-syntax.html" class="nav-chapters next" title="You can navigate through the chapters using the arrow keys">
|
||
<i class="fa fa-angle-right"></i>
|
||
</a>
|
||
|
||
|
||
</div>
|
||
|
||
|
||
<!-- Local fallback for Font Awesome -->
|
||
<script>
|
||
if ($(".fa").css("font-family") !== "FontAwesome") {
|
||
$('<link rel="stylesheet" type="text/css" href="_FontAwesome/css/font-awesome.css">').prependTo('head');
|
||
}
|
||
</script>
|
||
|
||
<!-- Livereload script (if served using the cli tool) -->
|
||
|
||
|
||
<script src="highlight.js"></script>
|
||
<script src="book.js"></script>
|
||
</body>
|
||
</html>
|