Codes in lesson20
trait 定义 trait 定义了某个特定类型拥有可能与其他类型共享的功能
trait 类似于其他语言中 接口 (interfaces )的功能
trait 实现 普通实现 使用 trait 关键字声明一个特征 在大括号中定义该特征的所有方法 只定义特征方法的签名,而不进行实现,此时方法签名结尾是 ;,而不是 {} 1 2 3 pub trait Summary { fn summarize (&self ) -> String ; }
为类型实现特征 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 pub trait Summary { fn summarize (&self ) -> String ; } pub struct Post { pub title: String , pub author: String , pub content: String , } impl Summary for Post { fn summarize (&self ) -> String { format! ("文章{}, 作者是{}" , self .title, self .author) } } pub struct Weibo { pub username: String , pub content: String } impl Summary for Weibo { fn summarize (&self ) -> String { format! ("{}发表了微博{}" , self .username, self .content) } }
特征定义与实现的位置(孤儿规则) 如果你想要为类型 A 实现特征 T,那么 A 或者 T 至少有一个是在当前作用域中定义的
可以为上面的 Post 类型实现标准库中的 Display 特征,这是因为 Post 类型定义在当前的作用域中 可以在当前包中为 String 类型实现 Summary 特征,因为 Summary 定义在当前作用域中 无法在当前作用域中,为 String 类型实现 Display 特征,因为它们俩都定义在标准库中,而不是当前的作用域 默认实现 1、其它类型无需再实现该方法,也可以选择重载该方法 1 2 3 4 5 pub trait Summary { fn summarize (&self ) -> String { String ::from ("(Read more...)" ) } }
Post 选择了默认实现,而 Weibo 重载了该方法
1 2 3 4 5 6 7 impl Summary for Post {}impl Summary for Weibo { fn summarize (&self ) -> String { format! ("{}发表了微博{}" , self .username, self .content) } }
2、默认实现 允许调用 相同特征中的其他方法,哪怕这些方法没有默认实现 1 2 3 4 5 6 7 8 9 10 11 12 13 pub trait Summary { fn summarize_author (&self ) -> String ; fn summarize (&self ) -> String { format! ("(Read more from {}...)" , self .summarize_author ()) } } impl Summary for Weibo { fn summarize_author (&self ) -> String { format! ("@{}" , self .username) } } println! ("1 new weibo: {}" , weibo.summarize ());
带泛型的 trait 可以对同一个目标类型,多次 impl 此 trait,每次提供不同的泛型参数 在具体方法调用的时候,必须通过类型标注 明确使用的是哪一个具体的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 trait Converter <T> { fn convert (&self ) -> T; } struct MyInt (i32 );impl Converter <String > for MyInt { fn convert (&self ) -> String { self .0 .to_string () } } impl Converter <f32 > for MyInt { fn convert (&self ) -> f32 { self .0 as f32 } } fn main () { let my_int = MyInt (42 ); let output : String = my_int.convert (); println! ("output is: {}" , output); let output : f32 = my_int.convert (); println! ("output is: {}" , output); }
关联类型 关联类型是 trait 定义中的类型占位符,定义时不指定其具体的类型,在实现(impl)该 trait 时,才为这个关联类型赋予确定的类型
关联类型方式只允许对目标类型实现一次
1 2 3 4 5 6 7 8 9 10 trait Converter { type Output ; fn convert (&self ) -> Self ::Output; } impl Converter for MyInt { type Output = String ; fn convert (&self ) -> Self ::Output { self .0 .to_string () } }
默认泛型类型参数 默认泛型的例子 1 2 3 4 5 6 fn add <T: std::ops::Add<Output = T>>(a: T, b: T) -> T { a + b } println! ("add i8: {}" , add (2i8 , 3i8 ));println! ("add f64: {}" , add (1.23 , 1.23 ));
默认泛型类型参数 加法之所以设置默认参数,是因为相同类型相加是最常见的情况
1 2 3 4 trait Add <Rhs=Self > { type Output ; fn add (self , rhs: Rhs) -> Self ::Output; }
<Rhs=Self> 定义了加法操作的右操作数类型:
Rhs 是 “Right-hand side”(右操作数)的缩写=Self 表示默认情况下右操作数与左操作数相同这是一个默认泛型参数语法,允许在实现时不显式指定类型 type Output; 定义加法操作的结果类型,具体类型在实现时确定
fn add(self, rhs: Rhs) -> Self::Output;
接受两个参数:左操作数(self)和右操作数(rhs) 返回 Output 关联类型定义的类型 使用 move 语义转移所有权 使用默认泛型类型参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 use std::ops::Add;struct Point { x: i32 , y: i32 , } impl Add for Point { type Output = Point; fn add (self , other: Point) -> Point { Point { x: self .x + other.x, y: self .y + other.y, } } } fn main () { assert_eq! ( Point { x: 1 , y: 0 } + Point { x: 2 , y: 3 }, Point { x: 3 , y: 3 } ); }
自定义 Rhs 类型而不使用默认类型 1 2 3 4 5 6 7 8 9 10 11 use std::ops::Add;struct Millimeters (u32 );struct Meters (u32 );impl Add <Meters> for Millimeters { type Output = Millimeters; fn add (self , other: Meters) -> Millimeters { Millimeters (self .0 + (other.0 * 1000 )) } }
使用特征作为函数参数 impl Trait 语法 可以使用任何实现了 Summary 特征的类型作为该函数的参数
1 2 3 pub fn notify (item: &impl Summary ) { println! ("Breaking news! {}" , item.summarize ()); }
除了单个约束条件,还可以通过 + 语法指定多个约束条件
1 pub fn notify (item: &(impl Summary + Display)) {}
Trait Bound 语法 impl Trait 适用于短小的例子,它是 trait bound 语法的语法糖更长的 trait bound 则适用于更复杂的场景 除了单个约束条件,我们还可以通过 + 语法指定多个约束条件 1 2 3 4 5 pub fn notify <T: Summary>(item: &T) { println! ("Breaking news! {}" , item.summarize ()); } pub fn notify <T: Summary + Display>(item: &T) {}
Trait Bound 能做到而 impl Trait 做不到的能力 1 pub fn notify (item1: &impl Summary , item2: &impl Summary ) {}
这适用于 item1 和 item2 允许是不同类型的情况(只要都实现了 Summary)。如果希望它们都是相同类型,就只能使用 trait bound 才能实现:
1 pub fn notify <T: Summary>(item1: &T, item2: &T) {
通过 where 简化 trait bound 1 fn some_function <T: Display + Clone , U: Clone + Debug >(t: &T, u: &U) -> i32 {}
1 2 3 4 fn some_function <T, U>(t: &T, u: &U) -> i32 where T: Display + Clone , U: Clone + Debug {}
使用特征约束有条件地实现方法或特征 实现方法 可以有条件地只为那些实现了特定 trait 的类型实现方法
只有那些为 T 类型实现了 PartialOrd trait(允许比较)和 Display trait(允许打印)的 Pair<T> 才会实现 cmp_display 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 use std::fmt::Display;struct Pair <T> { x: T, y: T, } impl <T> Pair<T> { fn new (x: T, y: T) -> Self { Self { x, y } } } impl <T: Display + 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); } } }
返回实现了 trait 的类型 impl Trait 语法在返回值中使用 impl Trait 语法,来返回实现了某个 trait 的类型
1 2 3 4 5 6 fn returns_summarizable () -> impl Summary { Weibo { username: String ::from ("horse_ebooks" ), content: String ::from ("of course, as you probably already know, people" ), } }
dyn trait 语法返回实现了某个 trait(动态 trait) 的类型
动态 trait 对象 指定某种指针例如 & 引用或 Box<T> 智能指针,还有 dyn keyword,以及指定相关的 trait 来创建 trait 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 fn returns_summarizable (switch: bool ) -> Box <dyn Summary> { if switch { Box ::new (Post { title: String ::from ( "Penguins win the Stanley Cup Championship!" , ), author: String ::from ("Iceburgh" ), content: String ::from ( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL." , ), }) } else { Box ::new (Weibo { username: String ::from ("horse_ebooks" ), content: String ::from ( "of course, as you probably already know, people" , ), }) } }
通过 derive 派生特征 形如 #[derive(Debug)] 的代码是一种特征派生语法,被 derive 标记的对象会自动实现对应的默认特征代码,继承相应的功能
Debug 特征有一套自动实现的默认代码,当给一个结构体标记后,就可以使用 println!("{:?}", s) 的形式打印该结构体的对象Copy 特征也有一套自动实现的默认代码,当标记到一个类型上时,可以让这个类型自动实现 Copy 特征,进而可以调用 copy 方法,进行自我复制1 2 3 4 5 6 7 8 9 10 #[derive(Debug, Clone)] struct Point { x: i32 , y: i32 , } fn main () { let p1 = { x: 0 , y: 1 }; let p2 = p1; println! ("p1: {:?}" , p1); }
用于程序员输出的 Debug Debug trait 用于开启格式化字符串中的调试格式,允许以调试目的来打印一个类型的实例,可以在程序执行的特定时间点观察其实例
例如,在使用 assert_eq! 宏时,Debug trait 是必须的。如果等式断言失败,这个宏就把给定实例的值作为参数打印出来,就能看到两个实例为什么不相等
默认值的 Default Default trait 会创建一个类型的默认值,Default 派生的实现调用了类型每部分的 default 函数,这意味着类型中所有的字段或值必须实现了 Default 才能派生 Default
1 2 3 4 5 6 7 #[derive(Default)] pub struct Post { pub title: String , pub author: String , pub content: String , } let post1 = Post::default ();
在 Option<T> 实例上使用 unwrap_or_default 方法时,Default trait 是必须的。如果 Option<T> 是 None,unwrap_or_default 将返回存储在 Option<T> 中 T 类型的 Default::default 的结果
1 let post2 = None .unwrap_or_default ();
练习题 trait 的定义与实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 use std::fmt::Display;trait Item <T = String > { type Output : Display; fn summarize (&self ) -> Self ::Output; } struct Apple { name: String , } impl Item for Apple { type Output = String ; fn summarize (&self ) -> String { self .name.to_string () } } struct Weibo { author: String , content: String , } impl Item for Weibo { type Output = String ; fn summarize (&self ) -> String { format! ("@{}:{}" , self .author, self .content) } } pub struct Container { items: Vec <Box <dyn Item<Output = String >>>, } impl Container { pub fn iterator (&self ) { for item in &self .items { println! ("{}" , item.summarize ()); } } } fn main () { let apple = Apple { name: "Apple" .to_string (), }; let w = Weibo { author: "weibo" .to_string (), content: "hello" .to_string (), }; let container = Container { items: vec! [Box ::new (apple), Box ::new (w)], }; container.iterator (); }