rust-by-practice/zh-CN/src/generics-traits/traits.md

470 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Traits
特征 Trait 可以告诉编译器一个特定的类型所具有的、且能跟其它类型共享的特性。我们可以使用特征通过抽象的方式来定义这种共享行为,还可以使用特征约束来限定一个泛型类型必须要具有某个特定的行为。
> Note: 特征跟其它语言的接口较为类似,但是仍然有一些区别
## 示例
```rust,editable
struct Sheep { naked: bool, name: String }
impl Sheep {
fn is_naked(&self) -> bool {
self.naked
}
fn shear(&mut self) {
if self.is_naked() {
// `Sheep` 结构体上定义的方法可以调用 `Sheep` 所实现的特征的方法
println!("{} is already naked...", self.name());
} else {
println!("{} gets a haircut!", self.name);
self.naked = true;
}
}
}
trait Animal {
// 关联函数签名;`Self` 指代实现者的类型
// 例如我们在为 Pig 类型实现特征时,那 `new` 函数就会返回一个 `Pig` 类型的实例,这里的 `Self` 指代的就是 `Pig` 类型
fn new(name: String) -> Self;
// 方法签名
fn name(&self) -> String;
fn noise(&self) -> String;
// 方法还能提供默认的定义实现
fn talk(&self) {
println!("{} says {}", self.name(), self.noise());
}
}
impl Animal for Sheep {
// `Self` 被替换成具体的实现者类型: `Sheep`
fn new(name: String) -> Sheep {
Sheep { name: name, naked: false }
}
fn name(&self) -> String {
self.name.clone()
}
fn noise(&self) -> String {
if self.is_naked() {
"baaaaah?".to_string()
} else {
"baaaaah!".to_string()
}
}
// 默认的特征方法可以被重写
fn talk(&self) {
println!("{} pauses briefly... {}", self.name, self.noise());
}
}
fn main() {
// 这里的类型注释时必须的
let mut dolly: Sheep = Animal::new("Dolly".to_string());
// TODO ^ 尝试去除类型注释,看看会发生什么
dolly.talk();
dolly.shear();
dolly.talk();
}
```
## Exercises
1. 🌟🌟
```rust,editable
// 完成两个 `impl` 语句块
// 不要修改 `main` 中的代码
trait Hello {
fn say_hi(&self) -> String {
String::from("hi")
}
fn say_something(&self) -> String;
}
struct Student {}
impl Hello for Student {
}
struct Teacher {}
impl Hello for Teacher {
}
fn main() {
let s = Student {};
assert_eq!(s.say_hi(), "hi");
assert_eq!(s.say_something(), "I'm a good student");
let t = Teacher {};
assert_eq!(t.say_hi(), "Hi, I'm your new teacher");
assert_eq!(t.say_something(), "I'm not a bad teacher");
println!("Success!")
}
```
### Derive 派生
我们可以使用 `#[derive]` 属性来派生一些特征,对于这些特征编译器会自动进行默认实现,对于日常代码开发而言,这是非常方便的,例如大家经常用到的 `Debug` 特征,就是直接通过派生来获取默认实现,而无需我们手动去完成这个工作。
想要查看更多信息,可以访问[这里](https://course.rs/appendix/derive.html)。
2. 🌟🌟
```rust,editable
// `Centimeters`, 一个元组结构体,可以被比较大小
#[derive(PartialEq, PartialOrd)]
struct Centimeters(f64);
// `Inches`, 一个元组结构体可以被打印
#[derive(Debug)]
struct Inches(i32);
impl Inches {
fn to_centimeters(&self) -> Centimeters {
let &Inches(inches) = self;
Centimeters(inches as f64 * 2.54)
}
}
// 添加一些属性让代码工作
// 不要修改其它代码!
struct Seconds(i32);
fn main() {
let _one_second = Seconds(1);
println!("One second looks like: {:?}", _one_second);
let _this_is_true = _one_second == _one_second;
let _this_is_true = _one_second > _one_second;
let foot = Inches(12);
println!("One foot equals {:?}", foot);
let meter = Centimeters(100.0);
let cmp =
if foot.to_centimeters() < meter {
"smaller"
} else {
"bigger"
};
println!("One foot is {} than one meter.", cmp);
}
```
### 运算符
在 Rust 中,许多运算符都可以被重载,事实上,运算符仅仅是特征方法调用的语法糖。例如 `a + b` 中的 `+` 是 `std::ops::Add` 特征的 `add` 方法调用,因此我们可以为自定义类型实现该特征来支持该类型的加法运算。
3. 🌟🌟
```rust,editable
use std::ops;
// 实现 fn multiply 方法
// 如上所述,`+` 需要 `T` 类型实现 `std::ops::Add` 特征
// 那么, `*` 运算符需要实现什么特征呢? 你可以在这里找到答案: https://doc.rust-lang.org/core/ops/
fn multiply
fn main() {
assert_eq!(6, multiply(2u8, 3u8));
assert_eq!(5.0, multiply(1.0, 5.0));
println!("Success!")
}
```
4. 🌟🌟🌟
```rust,editable
// 修复错误不要修改 `main` 中的代码!
use std::ops;
struct Foo;
struct Bar;
struct FooBar;
struct BarFoo;
// 下面的代码实现了自定义类型的相加 Foo + Bar = FooBar
impl ops::Add<Bar> for Foo {
type Output = FooBar;
fn add(self, _rhs: Bar) -> FooBar {
FooBar
}
}
impl ops::Sub<Foo> for Bar {
type Output = BarFoo;
fn sub(self, _rhs: Foo) -> BarFoo {
BarFoo
}
}
fn main() {
// 不要修改下面代码
// 你需要为 FooBar 派生一些特征来让代码工作
assert_eq!(Foo + Bar, FooBar);
assert_eq!(Foo - Bar, BarFoo);
println!("Success!")
}
```
### 使用特征作为函数参数
除了使用具体类型来作为函数参数,我们还能通过 `impl Trait` 的方式来指定实现了该特征的参数:该参数能接受的类型必须要实现指定的特征。
5. 🌟🌟🌟
```rust,editable
// 实现 `fn summary`
// 修复错误且不要移除任何代码行
trait Summary {
fn summarize(&self) -> String;
}
#[derive(Debug)]
struct Post {
title: String,
author: String,
content: String,
}
impl Summary for Post {
fn summarize(&self) -> String {
format!("The author of post {} is {}", self.title, self.author)
}
}
#[derive(Debug)]
struct Weibo {
username: String,
content: String,
}
impl Summary for Weibo {
fn summarize(&self) -> String {
format!("{} published a weibo {}", self.username, self.content)
}
}
fn main() {
let post = Post {
title: "Popular Rust".to_string(),
author: "Sunface".to_string(),
content: "Rust is awesome!".to_string(),
};
let weibo = Weibo {
username: "sunface".to_string(),
content: "Weibo seems to be worse than Tweet".to_string(),
};
summary(post);
summary(weibo);
println!("{:?}", post);
println!("{:?}", weibo);
}
// 在下面实现 `fn summary` 函数
```
### 使用特征作为函数返回值
我们还可以在函数的返回值中使用 `impl Trait` 语法。然后只有在返回值是同一个类型时,才能这么使用,如果返回值是不同的类型,你可能更需要特征对象。
6. 🌟🌟
```rust,editable
struct Sheep {}
struct Cow {}
trait Animal {
fn noise(&self) -> String;
}
impl Animal for Sheep {
fn noise(&self) -> String {
"baaaaah!".to_string()
}
}
impl Animal for Cow {
fn noise(&self) -> String {
"moooooo!".to_string()
}
}
// 返回一个类型,该类型实现了 Animal 特征,但是我们并不能在编译期获知具体返回了哪个类型
// 修复这里的错误,你可以使用虚假的随机,也可以使用特征对象
fn random_animal(random_number: f64) -> impl Animal {
if random_number < 0.5 {
Sheep {}
} else {
Cow {}
}
}
fn main() {
let random_number = 0.234;
let animal = random_animal(random_number);
println!("You've randomly chosen an animal, and it says {}", animal.noise());
}
```
### 特征约束
`impl Trait` 语法非常直观简洁,但它实际上是特征约束的语法糖。
当使用泛型参数时,我们往往需要为该参数指定特定的行为,这种指定方式就是通过特征约束来实现的。
7. 🌟🌟
```rust,editable
fn main() {
assert_eq!(sum(1, 2), 3);
}
// 通过两种方法使用特征约束来实现 `fn sum`
fn sum<T>(x: T, y: T) -> T {
x + y
}
```
8. 🌟🌟
```rust,editable
// 修复代码中的错误
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self {
x,
y,
}
}
}
impl<T: std::fmt::Debug + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {:?}", self.x);
} else {
println!("The largest member is y = {:?}", self.y);
}
}
}
struct Unit(i32);
fn main() {
let pair = Pair{
x: Unit(1),
y: Unit(3)
};
pair.cmp_display();
}
```
9. 🌟🌟🌟
```rust,editable
// 填空
fn example1() {
// `T: Trait` 是最常使用的方式
// `T: Fn(u32) -> u32` 说明 `T` 只能接收闭包类型的参数
struct Cacher<T: Fn(u32) -> u32> {
calculation: T,
value: Option<u32>,
}
impl<T: Fn(u32) -> u32> Cacher<T> {
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
},
}
}
}
let mut cacher = Cacher::new(|x| x+1);
assert_eq!(cacher.value(10), __);
assert_eq!(cacher.value(15), __);
}
fn example2() {
// 还可以使用 `where` 来约束 T
struct Cacher<T>
where T: Fn(u32) -> u32,
{
calculation: T,
value: Option<u32>,
}
impl<T> Cacher<T>
where T: Fn(u32) -> u32,
{
fn new(calculation: T) -> Cacher<T> {
Cacher {
calculation,
value: None,
}
}
fn value(&mut self, arg: u32) -> u32 {
match self.value {
Some(v) => v,
None => {
let v = (self.calculation)(arg);
self.value = Some(v);
v
},
}
}
}
let mut cacher = Cacher::new(|x| x+1);
assert_eq!(cacher.value(20), __);
assert_eq!(cacher.value(25), __);
}
fn main() {
example1();
example2();
println!("Success!")
}
```
> 你可以在[这里](https://github.com/sunface/rust-by-practice/blob/master/solutions/generics-traits/traits.md)找到答案(在 solutions 路径下)