diff --git a/projects/traits_example/src/main.rs b/projects/traits_example/src/main.rs index 97a454b..e036277 100644 --- a/projects/traits_example/src/main.rs +++ b/projects/traits_example/src/main.rs @@ -1,34 +1,31 @@ -trait Pilot { - fn fly(&self); -} +use std::fmt; -trait Wizard { - fn fly(&self); -} - -struct Human; - -impl Pilot for Human { - fn fly(&self) { - println! ("这是你们的机长在讲话。"); +trait OutlinePrint: fmt::Display { + fn outline_print(&self) { + let output = self.to_string(); + let len = output.len(); + println!("{}", "*".repeat(len + 4)); + println!("*{}*", " ".repeat(len + 2)); + println!("* {output} *"); + println!("*{}*", " ".repeat(len + 2)); + println!("{}", "*".repeat(len + 4)); } } -impl Wizard for Human { - fn fly(&self) { - println! ("上升!"); - } +struct Point { + x: i32, + y: i32, } -impl Human { - fn fly(&self) { - println! ("*疯狂地挥舞双臂*"); +impl OutlinePrint for Point {} + +impl fmt::Display for Point { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write! (f, "({}, {})", self.x, self.y) } } fn main() { - let person = Human; - Pilot::fly(&person); - Wizard::fly(&person); - person.fly(); + let p = Point { x: 1, y: 3 }; + p.outline_print(); } diff --git a/src/advanced_features/adv_traits.md b/src/advanced_features/adv_traits.md index ecef284..d94e055 100644 --- a/src/advanced_features/adv_traits.md +++ b/src/advanced_features/adv_traits.md @@ -228,10 +228,11 @@ $ cargo run *疯狂地挥舞双臂* ``` -由于 `fly` 方法取了一个 `self` 参数,那么当咱们有着实现了一个 *特质* 的两个 *类型* 时,Rust 就可以根据 `self` 的类型,找出要使用特质的哪个实现。 +由于 `fly` 方法取一个 `self` 参数,因此当我们有两种都实现了同一个 *特质* 的 *类型* 时,Rust 可以根据 `self` 的类型来确定要使用哪个特质的实现。 -然而,不是方法的那些关联函数,是没有 `self` 参数的。当存在以同样函数名字,定义了非方法函数的类型或特质时,除非咱们使用了 *完全合格语法,fully qualified syntax*,否则 Rust 就不会总是清楚咱们所指的是何种类型。比如,在下面清单 19-19 中,咱们创建了一个用于动物收容所的特质,其中打算将所有狗崽都命名为 `点点`。咱们构造了带有关联的非方法函数 `baby_name` 的一个 `Animal` 特质。对结构体 `Dog` 实现了这个 `Animal` 特质,在 `Dog` 上咱们还直接提供了一个关联的非方法函数 `baby_name`。 +然而,不是方法的关联函数没有 `self` 参数。当存在多个类型或特质,以相同的函数名字定义了非方法的函数时,除非咱们使用 *完全限定语法,fully qualified syntax*,否则 Rust 并不总是知道咱们所指的是何种类型。例如,在下面清单 20-20 中,我们为动物收容所创建了一个特质,他们打算将所有狗崽取名为点点。我们以一个关联的非方法函数,构造了一个 `Animal` 特质。`Animal` 特质针对结构体 `Dog` 予以实现,我们还在 `Dog` 上直接提供了一个关联的非方法函数 `baby_name`。 + ```rust trait Animal { fn baby_name() -> String; @@ -247,7 +248,7 @@ impl Dog { impl Animal for Dog { fn baby_name() -> String { - String::from("Puppy") + String::from("小狗") } } @@ -256,55 +257,58 @@ fn main() { } ``` -*清单 19-19:有着一个关联函数的特质以及一个有着同样函数名字关联函数、还实现了那个特质的类型* +**清单 20-20**:一个带有关联函数的特质,以及一个有着同名关联函数且还实现了该特质的类型 -咱们是在那个定义在 `Dog` 上的关联函数里,实现的将全部狗仔命名为点点的代码。`Dog` 类型还实现了特质 `Animal`,该特质描述了全部动物都有的特征。小狗都叫做狗崽,且这一点是在 `Dog` 上的 `Animal` 特质中,与 `Animal` 特质关联的 `baby_name` 函数中得以表达的。 +我们在于 `Dog` 上定义的 `baby_name` 关联函数中,实现了将所有狗崽取名为点点的代码。`Dog` 类型还实现了 `Animal` 特质,该特质描述了所有动物共有的特征。狗崽都叫做小狗,这一点在对 `Dog` 的 `Animal` 特质实现中,在与 `Animal` 特质关联的 `baby_name` 函数中得以表达。 -在 `main` 函数中,咱们调用了那个 `Dog::baby_name` 函数,这就会调用直接定义在 `Dog` 上的那个关联函数。此代码会打印下面的输出: +在 `main` 中,我们调用了 `Dog::baby_name` 函数,他会直接调用定义在 `Dog` 上的关联函数。这段代码会打印以下输出: ```console $ cargo run - Finished dev [unoptimized + debuginfo] target(s) in 0.00s - Running `target/debug/disambiguation` + Compiling traits_example v0.1.0 (/home/hector/rust-lang-zh_CN/projects/traits_example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s + Running `target/debug/traits_example` 狗崽叫做 点点 ``` -此输出不是咱们想要的。咱们想要调用作为咱们曾在 `Dog` 上实现过的 `Animal` 特质一部分的那个 `baby_name` 函数,从而代码会打印出 `小狗叫做 狗崽`。咱们曾在清单 19-18 中用到的指定特质名字的技巧,这里就不管用了;而若咱们将 `main` 修改为下面清单 19-20 中的代码,咱们就将收到一个编译报错。 +这种输出不是我们想要的。我们希望调用我们对 `Dog` 实现的 `Animal` 特质的一部分的 `baby_name` 函数,以便代码打印 `狗崽叫做 小狗`。我们在清单 20-19 中使用的指定特质名字的技巧在这里不管用;若我们修改 `main` 为下面清单 20-21 中的代码,我们将得到一个编译报错。 + ```rust fn main() { - println! ("小狗叫做 {}", Animal::baby_name()); + println! ("狗崽叫做 {}", Animal::baby_name()); } ``` -*清单 19-20:尝试调用 `Animal` 特质中的那个 `baby_name` 函数,但 Rust 不清楚要使用那个实现* +**清单 20-21**:尝试调用 `Animal` 特质中的 `baby_name` 函数,但 Rust 不知道要使用哪个实现 -由于 `Animal::baby_name` 没有 `self` 参数,且这里可能有别的实现了 `Animal` 特质的类型,因此 Rust 就无法计算出咱们想要的那个 `Animal::baby_name` 实现。咱们将得到下面这个编译器错误: +由于 `Animal::baby_name` 没有 `self` 参数,并且可能有其他类型实现了 `Animal` 特质,因此 Rust 无法计算出我们想要使用哪个 `Animal::baby_name` 实现。我们将得到以下编译器报错: ```console $ cargo run - Compiling disambiguation v0.1.0 (/home/lenny.peng/rust-lang/disambiguation) + Compiling traits_example v0.1.0 (/home/hector/rust-lang-zh_CN/projects/traits_example) error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type --> src/main.rs:20:26 | -2 | fn baby_name() -> String; + 2 | fn baby_name() -> String; | ------------------------- `Animal::baby_name` defined here ... -20 | println! ("小狗叫做 {}", Animal::baby_name()); - | ^^^^^^^^^^^^^^^^^ cannot call associated function of trait +20 | println! ("狗崽叫做 {}", Animal::baby_name()); + | ^^^^^^^^^^^^^^^^^^^ cannot call associated function of trait | help: use the fully-qualified path to the only available implementation | -20 | println! ("小狗叫做 {}", ::baby_name()); +20 | println! ("狗崽叫做 {}", ::baby_name()); | +++++++ + For more information about this error, try `rustc --explain E0790`. -error: could not compile `disambiguation` due to previous error +error: could not compile `traits_example` (bin "traits_example") due to 1 previous error ``` -为消除歧义并告知 Rust 咱们打算使用 `Dog` 的那个 `Animal` 实现,而非某种其他类型的 `Animal` 实现,咱们需要使用完全合格语法。下面清单 19-21 演示了怎样使用完全合格语法。 +为了消除歧义,并告知 Rust 我们想要使用针对 `Dog` 的 `Animal` 实现,而不是其他类型的 `Animal` 实现,我们需要使用完全限定语法。下面清单 20-22 演示了怎样使用完全限定语法。 + 文件名:`src/main.rs` ```rust @@ -313,38 +317,31 @@ fn main() { } ``` -*清单 19-21:使用完全合格语法来指明,咱们是要调用实现在 `Dog` 上的 `Animal` 特质中的那个 `baby_name` 函数* - -通过讲出咱们希望将 `Dog` 类型,针对这个 `baby_name` 函数调用而作为 `Animal` 对待,从而表明咱们打算调用实现在 `Dog` 上的 `Animal` 特质中的 `baby_name` 方法,这样位处那尖括号中的类型注解,提供给 Rust。此代码现在将打印出咱们想要的输出: +**清单 20-22**:使用完全限定语法来指定我们希望调用对 `Dog` 实现的 `Animal` 特质中的 `baby_name` 函数 +我们向 Rust 提供了尖括号内的类型注解,这表明我们希望调用对 `Dog` 实现的 `Animal` 特质中的 `baby_name` 函数,即表明我们希望针对这次函数调用,将 `Dog` 类型视为 `Animal`。这段代码现在将打印我们想要的输出: ```console $ cargo run - Compiling disambiguation v0.1.0 (/home/lenny.peng/rust-lang/disambiguation) - Finished dev [unoptimized + debuginfo] target(s) in 0.18s - Running `target/debug/disambiguation` -小狗叫做 狗崽 + Compiling traits_example v0.1.0 (/home/hector/rust-lang-zh_CN/projects/traits_example) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.07s + Running `target/debug/traits_example` +狗崽叫做 小狗 ``` -一般来讲,完全合格语法是像下面这样定义的: - +一般而言,完全限定语法定义如下: ```rust ::function(receiver_if_method, next_arg, ...); ``` -对于那些不是方法的语法,此处就不会有 `receiver`:这里将只有其他参数的清单。在调用函数或方法的所有地方,咱们都可以使用完全合格语法。不过,在 Rust 能够从程序中另外的信息计算出(要调用哪个函数或方法)时,那么这种语法便是可以省略的。咱们只需在有着多个使用了同一名字的实现,且 Rust 需要帮助来识别出咱们打算调用哪个实现时,才需要使用这种更为冗长的语法。 +对于不属于方法的关联函数,就不存在 `receiver`:只有其他参数的列表。咱们可以在调用函数或方法的任何地方使用完全限定语法。但是,对于 Rust 可以从程序中的其他信息计算出的部分,咱们可以省略这种语法的任何部分。仅在存在多个使用相同名字的实现,并且 Rust 需要帮助来时别咱们想要调用哪个实现时,咱们才需要使用这种更详细的语法。 +## 使用超特质 -## 在一个特质里运用超特质寻求另一特质的功能 - -**Using Supertraits to Require One Trait's Functionality Within Another Trait** - - -有的时候,咱们可能会编写依赖于另一特质的特质:对于要实现前一个特质的类型,咱们希望寻求那个类型也实现后一个特质。为了咱们的特质定义,可以利用后一个特质的那些关联项目,咱们就会实现这一点。咱们的特质所依赖的那个特质,被称为咱们特质的 *超特质,supertrait*。 - -比方说,咱们打算构造一个带有将所给的值格式化,从而其被星号框起来的 `outline_print` 方法,这样一个 `OutlinePrint` 特质。而那个所给的值则是,一个实现了标准库特质 `Display` 来得到 `(x, y)` 的 `Point` 结构体,即当咱们在有着 `x` 为 `1` `y` 为 `3` 的 `Point` 上调用 `outline_print` 时,其将打印以下输出: +有时,咱们可能会编写一个依赖于另一特质的特质定义:为了让某种类型实现前一个特质,咱们会希望要求该类型也实现后一个特质。咱们之所以会这样做,是为了让咱们的特质定义可以使用后一个特质中的关联项目。咱们的特质所依赖的特质,被称为咱们特质的 *超特质,supertrait*。 +例如,假设我们打算构造一个带有 `outline_print` 方法的 `OutlinePrint` 特质,该方法将打印格式化的给定值,使其被星号框起来。也就是说,假设有个实现标准库特质 `Display` 以得到 `(x, y)` 的 `Point` 结构体,当我们对 `x` 为 `1`、`y` 为 `3` 的 `Point` 调用 `outline_print` 时,他应打印以下内容: ```console ********** @@ -354,9 +351,9 @@ $ cargo run ********** ``` -在 `outline_print` 方法的实现中,咱们打算使用 `Display` 特质的功能。因此,咱们就需要指明,这个 `OutlinePrint` 特质将只对那些同时实现了 `Display` 生效,且提供了 `OutlinePrint` 所需的功能。咱们可以通过指明 `OutlinePrint: Display`,在该特质定义中实现那一点。这种技巧类似于给特质添加特质边界。下面清单 19-22 给出了这个 `OutlinePrint` 特质的一种实现。 - +在 `outline_print` 方法的实现中,我们打算使用 `Display` 特质的功能。因此,我们需要指定 `OutlinePrint` 特质仅适用于同时实现 `Display`,且提供 `OutlinePrint` 所需功能的类型。我们可以通过指定 `OutlinePrint: Display`,在特质定义中实现这点。这种技巧类似于添加特质边界到特质。下面清单 20-23 展示了 `OutlinePrint` 特质的实现。 + ```rust use std::fmt; @@ -364,21 +361,20 @@ trait OutlinePrint: fmt::Display { fn outline_print(&self) { let output = self.to_string(); let len = output.len(); - println! ("{}", "*".repeat(len + 4)); println! ("*{}*", " ".repeat(len + 2)); - println! ("* {} *", output); + println! ("* {output} *"); println! ("*{}*", " ".repeat(len + 2)); println! ("{}", "*".repeat(len + 4)); } } ``` -*清单 19-22:需要 `Display` 中功能的 `OutlinePrint` 特质实现* +**清单 20-23**:实现需要 `Display` 中功能的 `OutlinePrint` 特质 -由于咱们已指明 `OutlinePrint` 需要 `Display` 特质,因此咱们就可以使用那个任何实现了 `Display` 类型上均已实现了的 `to_string` 函数。若咱们在没有于特质名字之后加上冒号并指明 `Display` 特质,便尝试使用 `to_string`,咱们就会得到一个声称当前作用域中的类型 `&Self` 下,未找到名为 `to_string` 的方法的报错。 +由于我们已指定 `OutlinePrint` 需要 `Display` 特质,因此我们可以使用 `to_string` 函数,他会针对实现 `Display` 的任何类型自动实现。若我们在没有于特质名字之后添加冒号并指定 `Display` 特质的情况下,尝试使用 `to_string`,我们就会得到一个报错,指出在当前作用域中找不到类型 `&Self` 的名为 `to_string` 的方法。 -下面来看看当咱们尝试在某个未实现 `Display` 的类型,比如 `Point` 结构体上,实现 `OutlinePrint` 时会发生什么: +我们来看看当我们尝试对某个未实现 `Display` 的类型,比如 `Point` 结构体,实现 `OutlinePrint` 时会发生什么: ```rust @@ -394,26 +390,48 @@ impl OutlinePrint for Point {} ```console $ cargo run - Compiling supertrait v0.1.0 (/home/lenny.peng/rust-lang/supertrait) + Compiling traits_example v0.1.0 (/home/hector/rust-lang-zh_CN/projects/traits_example) error[E0277]: `Point` doesn't implement `std::fmt::Display` - --> src/main.rs:21:23 + --> src/main.rs:20:23 | -21 | impl OutlinePrint for Point {} - | ^^^^^ `Point` cannot be formatted with the default formatter +20 | impl OutlinePrint for Point {} + | ^^^^^ unsatisfied trait bound | - = help: the trait `std::fmt::Display` is not implemented for `Point` - = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead +help: the trait `std::fmt::Display` is not implemented for `Point` + --> src/main.rs:15:1 + | +15 | struct Point { + | ^^^^^^^^^^^^ note: required by a bound in `OutlinePrint` --> src/main.rs:3:21 | -3 | trait OutlinePrint: fmt::Display { + 3 | trait OutlinePrint: fmt::Display { | ^^^^^^^^^^^^ required by this bound in `OutlinePrint` +error[E0277]: `Point` doesn't implement `std::fmt::Display` + --> src/main.rs:24:7 + | +24 | p.outline_print(); + | ^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `std::fmt::Display` is not implemented for `Point` + --> src/main.rs:15:1 + | +15 | struct Point { + | ^^^^^^^^^^^^ +note: required by a bound in `OutlinePrint::outline_print` + --> src/main.rs:3:21 + | + 3 | trait OutlinePrint: fmt::Display { + | ^^^^^^^^^^^^ required by this bound in `OutlinePrint::outline_print` + 4 | fn outline_print(&self) { + | ------------- required by a bound in this associated function + For more information about this error, try `rustc --explain E0277`. -error: could not compile `supertrait` due to previous error +error: could not compile `traits_example` (bin "traits_example") due to 2 previous errors ``` -为修复这个问题,咱们就要在 `Point` 上实现 `Display` 并满足 `OutlinePrint` 所需的约束,如下面这样: +为了修复这个问题,我们对 `Point` 实现 `Display`,从而满足 `OutlinePrint` 所需的约束条件,像下面这样: ```rust impl fmt::Display for Point { @@ -423,12 +441,11 @@ impl fmt::Display for Point { } ``` -随后在 `Point` 上实现 `OutlinePrint` 就将成功编译,而咱们就可以在 `Point` 实例上调用 `outline_print` 来将其实现在星号轮廓里了。 +然后,对 `Point` 实现 `OutlinePrint` 特质将成功编译,我们就可以对 `Point` 实例调用 `outline_print` 方法,将其显示在由星号构成的轮廓内。 ## 通过新型模式实现外部特质 - 第 10 章中的 [“在类型上实现特质”](Ch10_Generic_Types_Traits_and_Lifetimes.md#在类型上实现某个特质) 小节,咱们曾提到,指明只有当特质或类型二者之一,属于代码本地的时,咱们才被允许在类型上实现特质的孤儿规则,the orphan rule。而使用涉及到在元组结构体中创建出一个新类型的 *新型模式,newtype pattern*,那么绕过这种限制便是可行的了。(咱们曾在第 5 章的 [“使用不带命名字段的元组结构体来创建不同类型”](Ch05_Using_Structs_to_Structure_Related_Data.md#使用不带命名字段的元组结构体来创建不同类型) 小节,谈到过元组结构体)这种元组结构体讲有一个字段,且将是围绕咱们要实现某个特质的类型的一个瘦封装,a thin wrapper。随后这个封装类型,便是咱们代码箱的本地类型了,而咱们就可以在这个封装上实现那个特质了。所谓 *新型,newtype*,是源自 Haskell 编程语言的一个术语。使用这种模式没有运行时性能代码,同时那个封装类型在编译时会被略去。 作为一个示例,就说咱们打算在 `Vec` 上实现 `Display`,而由于 `Display` 特质与 `Vec` 类型,均被定义在咱们代码箱外部,因此孤儿规则会阻止咱们直接这样做。咱们可以构造一个保存着 `Vec` 类型实例的 `Wrapper`;随后咱们就可以在 `Wrapper` 上实现 `Display`,并使用那个 `Vec` 值,如下清单 19-23 中所示。