<!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></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/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> <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> </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>