mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-13 03:21:20 +08:00
354 lines
32 KiB
HTML
354 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"><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" class="active"><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/ch17-02-trait-objects.md">ch17-02-trait-objects.md</a>
|
||
<br>
|
||
commit 67876e3ef5323ce9d394f3ea6b08cb3d173d9ba9</p>
|
||
</blockquote>
|
||
<p>在第八章,我们谈到了 vector 只能存储同种类型元素的局限。在列表 8-1 中有一个例子,其中定义了存放包含整型、浮点型和文本型成员的枚举类型<code>SpreadsheetCell</code>,这样就可以在每一个单元格储存不同类型的数据,并使得 vector 仍然代表一行单元格。当编译时就知道类型集合全部元素的情况下,这种方案是可行的。</p>
|
||
<!-- The code example I want to reference did not have a listing number; it's
|
||
the one with SpreadsheetCell. I will go back and add Listing 8-1 next time I
|
||
get Chapter 8 for editing. /Carol -->
|
||
<p>有时,我们希望使用的类型的集合对于使用库的程序员来说是可扩展的。例如,很多图形用户接口(GUI)工具有一个条目列表的概念,它通过遍历列表并对每一个条目调用 <code>draw</code> 方法来绘制在屏幕上。我们将要创建一个叫做 <code>rust_gui</code> 的包含一个 GUI 库结构的库 crate。GUI 库可以包含一些供开发者使用的类型,比如 <code>Button</code> 或 <code>TextField</code>。使用 <code>rust_gui</code> 的程序员会想要创建更多可以绘制在屏幕上的类型:一个程序员可能会增加一个 <code>Image</code>,而另一个可能会增加一个 <code>SelectBox</code>。我们不会在本章节实现一个功能完善的 GUI 库,不过会展示各个部分是如何结合在一起的。</p>
|
||
<p>当写 <code>rust_gui</code> 库时,我们不知道其他程序员需要什么类型,所以无法定义一个 <code>enum</code> 来包含所有的类型。然而 <code>rust_gui</code> 需要跟踪所有这些不同类型的值,需要有在每个值上调用 <code>draw</code> 方法能力。我们的 GUI 库不需要确切地知道调用 <code>draw</code> 方法会发生什么,只需要有可用的方法供我们调用。</p>
|
||
<p>在可以继承的语言里,我们会定义一个名为 <code>Component</code> 的类,该类上有一个<code>draw</code>方法。其他的类比如<code>Button</code>、<code>Image</code>和<code>SelectBox</code>会从<code>Component</code>继承并拥有<code>draw</code>方法。它们各自覆写<code>draw</code>方法以自定义行为,但是框架会把所有的类型当作是<code>Component</code>的实例,并在其上调用<code>draw</code>。</p>
|
||
<a class="header" href="#定义一个带有自定义行为的trait" name="定义一个带有自定义行为的trait"><h3>定义一个带有自定义行为的Trait</h3></a>
|
||
<p>不过,在Rust语言中,我们可以定义一个 <code>Draw</code> trait,包含名为 <code>draw</code> 的方法。我们定义一个由<em>trait对象</em>组成的vector,绑定了某种指针的trait,比如<code>&</code>引用或者一个<code>Box<T></code>智能指针。</p>
|
||
<p>之前提到,我们不会称结构体和枚举为对象,以区分其他语言的结构体和枚举对象。结构体或者枚举成员中的数据和<code>impl</code>块中的行为是分开的,而其他语言则是数据和行为被组合到一个对象里。Trait 对象更像其他语言的对象,因为他们将其指针指向的具体对象作为数据,将在 trait 中定义的方法作为行为,组合在了一起。但是,trait 对象和其他语言是不同的,我们不能向一个 trait 对象增加数据。trait 对象不像其他语言那样有用:它们的目的是允许从公有行为上抽象。</p>
|
||
<p>trait 对象定义了给定情况下应有的行为。当需要具有某种特性的不确定具体类型时,我们可以把 trait 对象当作 trait 使用。Rust 的类型系统会保证我们为 trait 对象带入的任何值会实现 trait 的方法。我们不需要在编译阶段知道所有可能的类型,却可以把所有的实例统一对待。列表 17-03 展示了如何定义一个名为<code>Draw</code>的带有<code>draw</code>方法的 trait。</p>
|
||
<p><span class="filename">文件名: src/lib.rs</span></p>
|
||
<pre><code class="language-rust">pub trait Draw {
|
||
fn draw(&self);
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">列表 17-3:<code>Draw</code> trait 的定义</span></p>
|
||
<!-- NEXT PARAGRAPH WRAPPED WEIRD INTENTIONALLY SEE #199 -->
|
||
<p>因为我们已经在第十章讨论过如何定义 trait,你可能比较熟悉。下面是新的定义:列表 17-4 有一个名为 <code>Screen</code> 的结构体,里面有一个名为 <code>components</code> 的 vector,<code>components</code> 的类型是 <code>Box<Draw></code>。<code>Box<Draw></code> 是一个 trait 对象:它是 <code>Box</code> 内部任意一个实现了 <code>Draw</code> trait 的类型的替身。</p>
|
||
<p><span class="filename">文件名: src/lib.rs</span></p>
|
||
<pre><code class="language-rust"># pub trait Draw {
|
||
# fn draw(&self);
|
||
# }
|
||
#
|
||
pub struct Screen {
|
||
pub components: Vec<Box<Draw>>,
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">列表 17-4: 一个 <code>Screen</code> 结构体的定义,它带有一个字段<code>components</code>,其包含实现了 <code>Draw</code> trait 的 trait 对象的 vector</span></p>
|
||
<p>在 <code>Screen</code> 结构体上,我们将要定义一个 <code>run</code> 方法,该方法会在它的 <code>components</code> 上的每一个元素调用 <code>draw</code> 方法,如列表 17-5 所示:</p>
|
||
<p><span class="filename">文件名: src/lib.rs</span></p>
|
||
<pre><code class="language-rust"># pub trait Draw {
|
||
# fn draw(&self);
|
||
# }
|
||
#
|
||
# pub struct Screen {
|
||
# pub components: Vec<Box<Draw>>,
|
||
# }
|
||
#
|
||
impl Screen {
|
||
pub fn run(&self) {
|
||
for component in self.components.iter() {
|
||
component.draw();
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">列表 17-5:在 <code>Screen</code> 上实现一个 <code>run</code> 方法,该方法在每个 component 上调用 <code>draw</code> 方法
|
||
</span></p>
|
||
<p>这与带 trait 约束的泛型结构体不同(trait 约束泛型参数)。泛型参数一次只能被一个具体类型替代,而 trait 对象可以在运行时允许多种具体类型填充 trait 对象。比如,我们已经定义了 <code>Screen</code> 结构体使用泛型和一个 trait 约束,如列表 17-6 所示:</p>
|
||
<p><span class="filename">文件名: src/lib.rs</span></p>
|
||
<pre><code class="language-rust"># pub trait Draw {
|
||
# fn draw(&self);
|
||
# }
|
||
#
|
||
pub struct Screen<T: Draw> {
|
||
pub components: Vec<T>,
|
||
}
|
||
|
||
impl<T> Screen<T>
|
||
where T: Draw {
|
||
pub fn run(&self) {
|
||
for component in self.components.iter() {
|
||
component.draw();
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">列表 17-6: 一种 <code>Screen</code> 结构体的替代实现,它的 <code>run</code> 方法使用通用类型和 trait 绑定
|
||
</span></p>
|
||
<p>这个例子中,<code>Screen</code> 实例所有组件类型必需全是 <code>Button</code>,或者全是 <code>TextField</code>。如果你的组件集合是单一类型的,那么可以优先使用泛型和 trait 约束,因为其使用的具体类型在编译阶段即可确定。</p>
|
||
<p>而 <code>Screen</code> 结构体内部的 <code>Vec<Box<Draw>></code> trait 对象列表,则可以同时包含 <code>Box<Button></code> 和 <code>Box<TextField></code>。我们看它是怎么工作的,然后讨论运行时性能。</p>
|
||
<a class="header" href="#来自我们或者库使用者的实现" name="来自我们或者库使用者的实现"><h3>来自我们或者库使用者的实现</h3></a>
|
||
<p>现在,我们增加一些实现了 <code>Draw</code> trait 的类型,再次提供 <code>Button</code>。实现一个 GUI 库实际上超出了本书的范围,因此 <code>draw</code> 方法留空。为了想象实现可能的样子,<code>Button</code> 结构体有 <code>width</code>、<code>height</code> 和 <code>label</code>字段,如列表 17-7 所示:</p>
|
||
<p><span class="filename">文件名: src/lib.rs</span></p>
|
||
<pre><code class="language-rust"># pub trait Draw {
|
||
# fn draw(&self);
|
||
# }
|
||
#
|
||
pub struct Button {
|
||
pub width: u32,
|
||
pub height: u32,
|
||
pub label: String,
|
||
}
|
||
|
||
impl Draw for Button {
|
||
fn draw(&self) {
|
||
// Code to actually draw a button
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">列表 17-7: 实一个现了<code>Draw</code> trait 的 <code>Button</code> 结构体</span></p>
|
||
<p>在 <code>Button</code> 上的 <code>width</code>、<code>height</code> 和 <code>label</code> 会和其他组件不同,比如 <code>TextField</code> 可能有 <code>width</code>、<code>height</code>,
|
||
<code>label</code> 以及 <code>placeholder</code> 字段。每个我们可以在屏幕上绘制的类型都会实现 <code>Draw</code> trait,在 <code>draw</code> 方法中使用不同的代码,定义了如何绘制 <code>Button</code>。除了 <code>Draw</code> trait,<code>Button</code> 也可能有一个 <code>impl</code> 块,包含按钮被点击时的响应方法。这类方法不适用于 <code>TextField</code> 这样的类型。</p>
|
||
<p>假定我们的库的用户相要实现一个包含 <code>width</code>、<code>height</code> 和 <code>options</code> 的 <code>SelectBox</code> 结构体。同时也在 <code>SelectBox</code> 类型上实现了 <code>Draw</code> trait,如 列表 17-8 所示:</p>
|
||
<p><span class="filename">文件名: src/main.rs</span></p>
|
||
<pre><code class="language-rust">extern crate rust_gui;
|
||
use rust_gui::Draw;
|
||
|
||
struct SelectBox {
|
||
width: u32,
|
||
height: u32,
|
||
options: Vec<String>,
|
||
}
|
||
|
||
impl Draw for SelectBox {
|
||
fn draw(&self) {
|
||
// Code to actually draw a select box
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">列表 17-8: 另外一个 crate 中,在 <code>SelectBox</code> 结构体上使用 <code>rust_gui</code> 和实现了<code>Draw</code> trait
|
||
</span></p>
|
||
<p>库的用户现在可以在他们的 <code>main</code> 函数中创建一个 <code>Screen</code> 实例,然后把自身放入 <code>Box<T></code> 变成 trait 对象,向 screen 增加 <code>SelectBox</code> 和 <code>Button</code>。他们可以在这个 <code>Screen</code> 实例上调用 <code>run</code> 方法,这又会调用每个组件的 <code>draw</code> 方法。 列表 17-9 展示了实现:</p>
|
||
<p><span class="filename">文件名: src/main.rs</span></p>
|
||
<pre><code class="language-rust">use rust_gui::{Screen, Button};
|
||
|
||
fn main() {
|
||
let screen = Screen {
|
||
components: vec![
|
||
Box::new(SelectBox {
|
||
width: 75,
|
||
height: 10,
|
||
options: vec![
|
||
String::from("Yes"),
|
||
String::from("Maybe"),
|
||
String::from("No")
|
||
],
|
||
}),
|
||
Box::new(Button {
|
||
width: 50,
|
||
height: 10,
|
||
label: String::from("OK"),
|
||
}),
|
||
],
|
||
};
|
||
|
||
screen.run();
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">列表 17-9: 使用 trait 对象来存储实现了相同 trait 的不同类型
|
||
</span></p>
|
||
<p>虽然我们不知道哪一天会有人增加 <code>SelectBox</code> 类型,但是我们的 <code>Screen</code> 能够操作 <code>SelectBox</code> 并绘制它,因为 <code>SelectBox</code> 实现了 <code>Draw</code> 类型,这意味着它实现了 <code>draw</code> 方法。</p>
|
||
<p>只关心值的响应,而不关心其具体类型,这类似于动态类型语言中的 <em>duck typing</em>:如果它像鸭子一样走路,像鸭子一样叫,那么它就是只鸭子!在 Listing 17-5 <code>Screen</code> 的 <code>run</code> 方法实现中,<code>run</code> 不需要知道每个组件的具体类型。它也不检查组件是 <code>Button</code> 还是 <code>SelectBox</code> 的实例,只管调用组件的 <code>draw</code> 方法。通过指定 <code>Box<Draw></code> 作为 <code>components</code> 列表中元素的类型,我们约束了 <code>Screen</code> 需要这些实现了 <code>draw</code> 方法的值。</p>
|
||
<p>Rust 类型系统使用 trait 对象来支持 duck typing 的好处是,我们无需在运行时检查一个值是否实现了特定方法,或是担心调用了一个值没有实现的方法。如果值没有实现 trait 对象需要的 trait(方法),Rust 不会编译。</p>
|
||
<p>比如,列表 17-10 展示了当我们创建一个使用 <code>String</code> 做为其组件的 <code>Screen</code> 时发生的情况:</p>
|
||
<p><span class="filename">文件名: src/main.rs</span></p>
|
||
<pre><code class="language-rust">extern crate rust_gui;
|
||
use rust_gui::Draw;
|
||
|
||
fn main() {
|
||
let screen = Screen {
|
||
components: vec![
|
||
Box::new(String::from("Hi")),
|
||
],
|
||
};
|
||
|
||
screen.run();
|
||
}
|
||
</code></pre>
|
||
<p><span class="caption">列表 17-10: 尝试使用一种没有实现 trait 对象的类型</p>
|
||
<p></span></p>
|
||
<p>我们会遇到这个错误,因为 <code>String</code> 没有实现 <code>Draw</code> trait:</p>
|
||
<pre><code>error[E0277]: the trait bound `std::string::String: Draw` is not satisfied
|
||
-->
|
||
|
|
||
4 | Box::new(String::from("Hi")),
|
||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Draw` is not
|
||
implemented for `std::string::String`
|
||
|
|
||
= note: required for the cast to the object type `Draw`
|
||
</code></pre>
|
||
<p>这个错误告诉我们,要么传入 <code>Screen</code> 需要的类型,要么在 <code>String</code> 上实现 <code>Draw</code>,以便 <code>Screen</code> 调用它的 <code>draw</code> 方法。</p>
|
||
<a class="header" href="#trait-对象执行动态分发" name="trait-对象执行动态分发"><h3>Trait 对象执行动态分发</h3></a>
|
||
<p>回忆一下第十章我们讨论过的,当我们在泛型上使用 trait 约束时,编译器按单态类型处理:在需要使用范型参数的地方,编译器为每个具体类型生成非泛型的函数和方法实现。单态类型处理产生的代码实际就是做 <em>static dispatch</em>:方法的代码在编译阶段就已经决定了,当调用时,寻找那段代码非常快速。</p>
|
||
<p>当我们使用 trait 对象,编译器不能按单态类型处理,因为无法知道使用代码的所有可能类型。而是调用方法的时候,Rust 跟踪可能被使用的代码,在运行时找出调用该方法时应使用的代码。这也是我们熟知的 <em>dynamic dispatch</em>,查找过程会产生运行时开销。动态分发也会阻止编译器内联函数,失去一些优化途径。尽管获得了额外的灵活性,但仍然需要权衡取舍。</p>
|
||
<a class="header" href="#trait-对象需要对象安全" name="trait-对象需要对象安全"><h3>Trait 对象需要对象安全</h3></a>
|
||
<!-- Liz: we're conflicted on including this section. Not being able to use a
|
||
trait as a trait object because of object safety is something that
|
||
beginner/intermediate Rust developers run into sometimes, but explaining it
|
||
fully is long and complicated. Should we just cut this whole section? Leave it
|
||
(and finish the explanation of how to fix the error at the end)? Shorten it to
|
||
a quick caveat, that just says something like "Some traits can't be trait
|
||
objects. Clone is an example of one. You'll get errors that will let you know
|
||
if a trait can't be a trait object, look up object safety if you're interested
|
||
in the details"? Thanks! /Carol -->
|
||
<p>不是所有的 trait 都可以被放进 trait 对象中; 只有<em>对象安全的</em>(<em>object safe</em>)trait 才可以这样做. 一个 trait 只有同时满足如下两点时才被认为是对象安全的:</p>
|
||
<ul>
|
||
<li>该 trait 要求 <code>Self</code> 不是 <code>Sized</code>;</li>
|
||
<li>该 trait 的所有方法都是对象安全的;</li>
|
||
</ul>
|
||
<p><code>Self</code> 是一个类型的别名关键字,它表示当前正被实现的 trait 类型或者是方法所属的类型. <code>Sized</code>是一个像在第十六章中介绍的<code>Send</code>和<code>Sync</code>那样的标记 trait, 在编译时它会自动被放进大小确定的类型里,比如<code>i32</code>和引用. 大小不确定的类型有 slice(<code>[T]</code>)和 trait 对象.</p>
|
||
<p><code>Sized</code> 是一个默认会被绑定到所有常规类型参数的内隐 trait. Rust 中要求一个类型是<code>Sized</code>的最具可用性的用法是让<code>Sized</code>成为一个默认的 trait 绑定,这样我们就可以在大多数的常规的用法中不去写 <code>T: Sized</code> 了. 如果我们想在切片(slice)中使用一个 trait, 我们需要取消对<code>Sized</code>的 trait 绑定, 我们只需制定<code>T: ?Sized</code>作为 trait 绑定.</p>
|
||
<p>默认绑定到 <code>Self: ?Sized</code> 的 trait 可以被实现到是 <code>Sized</code> 或非 <code>Sized</code> 的类型上. 如果我们创建一个不绑定 <code>Self: ?Sized</code> 的 trait <code>Foo</code>,它看上去应该像这样:</p>
|
||
<pre><code class="language-rust">trait Foo: Sized {
|
||
fn some_method(&self);
|
||
}
|
||
</code></pre>
|
||
<p>Trait <code>Sized</code>现在就是 trait <code>Foo</code>的一个<em>超级 trait</em>(<em>supertrait</em>), 也就是说 trait <code>Foo</code> 需要实现了 <code>Foo</code> 的类型(即<code>Self</code>)是<code>Sized</code>. 我们将在第十九章中更详细的介绍超 trait(supertrait).</p>
|
||
<p>像<code>Foo</code>那样要求<code>Self</code>是<code>Sized</code>的 trait 不允许成为 trait 对象的原因是不可能为 trait 对象<code>Foo</code>实现 trait <code>Foo</code>: trait 对象是无确定大小的,但是 <code>Foo</code> 要求 <code>Self</code> 是 <code>Sized</code>. 一个类型不可能同时既是有大小的又是无确定大小的.</p>
|
||
<p>第二点说对象安全要求一个 trait 的所有方法必须是对象安全的. 一个对象安全的方法满足下列条件:</p>
|
||
<ul>
|
||
<li>它要求 <code>Self</code> 是 <code>Sized</code> 或者</li>
|
||
<li>它符合下面全部三点:
|
||
<ul>
|
||
<li>它不包含任意类型的常规参数</li>
|
||
<li>它的第一个参数必须是类型 <code>Self</code> 或一个引用到 <code>Self</code> 的类型(也就是说它必须是一个方法而非关联函数并且以 <code>self</code>、<code>&self</code> 或 <code>&mut self</code> 作为第一个参数)</li>
|
||
<li>除了第一个参数外它不能在其它地方用 <code>Self</code> 作为方法的参数签名</li>
|
||
</ul>
|
||
</li>
|
||
</ul>
|
||
<p>虽然这些规则有一点形式化, 但是换个角度想一下: 如果你的方法在它的参数签名的其它地方也需要具体的 <code>Self</code> 类型参数, 但是一个对象又忘记了它的具体类型是什么, 这时该方法就无法使用被它忘记的原先的具体类型. 当该 trait 被使用时, 被具体类型参数填充的常规类型参数也是如此: 这个具体的类型就成了实现该 trait 的类型的某一部分, 如果使用一个 trait 对象时这个类型被抹掉了, 就没有办法知道该用什么类型来填充这个常规类型参数.</p>
|
||
<p>一个 trait 的方法不是对象安全的一个例子是标准库中的 <code>Clone</code> trait. <code>Clone</code> trait 的 <code>clone</code> 方法的参数签名是这样的:</p>
|
||
<pre><code class="language-rust">pub trait Clone {
|
||
fn clone(&self) -> Self;
|
||
}
|
||
</code></pre>
|
||
<p><code>String</code> 实现了 <code>Clone</code> trait, 当我们在一个 <code>String</code> 实例上调用 <code>clone</code> 方法时, 我们会得到一个 <code>String</code> 实例. 同样地, 如果我们在一个 <code>Vec</code> 实例上调用 <code>clone</code> 方法, 我们会得到一个 <code>Vec</code> 实例. <code>clone</code> 的参数签名需要知道 <code>Self</code> 是什么类型, 因为它需要返回这个类型.</p>
|
||
<p>如果我们像列表 17-3 中列出的 <code>Draw</code> trait 那样的 trait 上实现 <code>Clone</code>, 我们就不知道 <code>Self</code> 将会是一个 <code>Button</code>, 一个 <code>SelectBox</code>, 或者是其它的在将来要实现 <code>Draw</code> trait 的类型.</p>
|
||
<p>如果你做了违反 trait 对象的对象安全性规则的事情, 编译器将会告诉你. 比如, 如果你实现在列表 17-4 中列出的 <code>Screen</code> 结构, 你想让该结构像这样持有实现了 <code>Clone</code> trait 的类型而不是 <code>Draw</code> trait:</p>
|
||
<pre><code class="language-rust">pub struct Screen {
|
||
pub components: Vec<Box<Clone>>,
|
||
}
|
||
</code></pre>
|
||
<p>我们将会得到下面的错误:</p>
|
||
<pre><code class="language-text">error[E0038]: the trait `std::clone::Clone` cannot be made into an object
|
||
-->
|
||
|
|
||
2 | pub components: Vec<Box<Clone>>,
|
||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `std::clone::Clone` cannot be
|
||
made into an object
|
||
|
|
||
= note: the trait cannot require that `Self : Sized`
|
||
</code></pre>
|
||
<!-- If we are including this section, we would explain how to fix this
|
||
problem. It involves adding another trait and implementing Clone manually for
|
||
that trait. Because this section is getting long, I stopped because it feels
|
||
like we're off in the weeds with an esoteric detail that not everyone will need
|
||
to know about. /Carol -->
|
||
|
||
</div>
|
||
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
<a href="ch17-01-what-is-oo.html" class="mobile-nav-chapters previous">
|
||
<i class="fa fa-angle-left"></i>
|
||
</a>
|
||
|
||
|
||
|
||
|
||
</div>
|
||
|
||
|
||
<a href="ch17-01-what-is-oo.html" class="nav-chapters previous" title="You can navigate through the chapters using the arrow keys">
|
||
<i class="fa fa-angle-left"></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>
|