2017-02-25 23:47:33 +08:00
<!DOCTYPE HTML>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
2017-02-26 15:11:54 +08:00
< title > trait: 定义共享的行为 - Rust 程序设计语言 简体中文版< / title >
2017-02-25 23:47:33 +08:00
< meta content = "text/html; charset=utf-8" http-equiv = "Content-Type" >
2017-02-26 15:11:54 +08:00
< meta name = "description" content = "Rust 程序设计语言 简体中文版" >
2017-02-25 23:47:33 +08:00
< 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" >
2017-03-18 21:39:27 +08:00
< 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-
2017-02-25 23:47:33 +08:00
< / 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 >
2017-02-26 15:11:54 +08:00
< h1 class = "menu-title" > Rust 程序设计语言 简体中文版< / h1 >
2017-02-25 23:47:33 +08:00
< div class = "right-buttons" >
< i id = "print-button" class = "fa fa-print" title = "Print this book" > < / i >
< / div >
< / div >
< div id = "content" class = "content" >
2017-02-26 21:34:40 +08:00
< a class = "header" href = "#trait定义共享的行为" name = "trait定义共享的行为" > < h2 > trait: 定义共享的行为< / h2 > < / a >
< blockquote >
< p > < a href = "https://github.com/rust-lang/book/blob/master/src/ch10-02-traits.md" > ch10-02-traits.md< / a >
< br >
commit 709eb1eaca48864fafd9263042f5f9d9d6ffe08d< / 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 >
< figure >
< span class = "filename" > Filename: lib.rs< / span >
< pre > < code class = "language-rust" > pub trait Summarizable {
fn summary(& self) -> String;
}
< / code > < / pre >
< figcaption >
< p > Listing 10-11: Definition of a < code > Summarizable< / code > trait that consists of the
behavior provided by a < code > summary< / code > method< / p >
< / figcaption >
< / figure >
< 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 >
< figure >
< span class = "filename" > Filename: lib.rs< / span >
< 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 >
< figcaption >
< p > Listing 10-12: Implementing the < code > Summarizable< / code > trait on the < code > NewsArticle< / code > and
< code > Tweet< / code > types< / p >
< / figcaption >
< / figure >
2017-02-27 23:25:11 +08:00
< 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 >
< figure >
< span class = "filename" > Filename: lib.rs< / span >
< 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 >
< figcaption >
< p > Listing 10-13: Bringing the < code > Summarizable< / code > trait from our < code > aggregator< / code > crate
into scope in another crate< / p >
< / figcaption >
< / figure >
< 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 >
< figure >
< span class = "filename" > Filename: lib.rs< / span >
< pre > < code class = "language-rust" > pub trait Summarizable {
fn summary(& self) -> String {
String::from(" (Read more...)" )
}
}
< / code > < / pre >
< figcaption >
< p > Listing 10-14: Definition of a < code > Summarizable< / code > trait with a default
implementation of the < code > summary< / code > method< / p >
< / figcaption >
< / figure >
< 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 >
< figure >
< span class = "filename" > Filename: src/main.rs< / span >
< 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 >
< figcaption >
< p > 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< / p >
< / figcaption >
< / figure >
< 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 >
2017-02-26 21:34:40 +08:00
2017-02-25 23:47:33 +08:00
< / 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 >