目录
特征对象
特征对象指向实现了特征的类型的实例
可以通过 & 引用或者 Box 智能指针的方式来创建特征对象。
trait Draw { fn draw(&self) -> String; } impl Draw for u8 { fn draw(&self) -> String { format!("u8: {}", *self) } } impl Draw for f64 { fn draw(&self) -> String { format!("f64: {}", *self) } } // 若 T 实现了 Draw 特征, 则调用该函数时传入的 Box<T> 可以被隐式转换成函数参数签名中的 Box<dyn Draw> fn draw1(x: Box<dyn Draw>) { // 由于实现了 Deref 特征,Box 智能指针会自动解引用为它所包裹的值,然后调用该值对应的类型上定义的 `draw` 方法 x.draw(); } fn draw2(x: &dyn Draw) { x.draw(); } fn main() { let x = 1.1f64; // do_something(&x); let y = 8u8; // x 和 y 的类型 T 都实现了 `Draw` 特征,因为 Box<T> 可以在函数调用时隐式地被转换为特征对象 Box<dyn Draw> // 基于 x 的值创建一个 Box<f64> 类型的智能指针,指针指向的数据被放置在了堆上 draw1(Box::new(x)); // 基于 y 的值创建一个 Box<u8> 类型的智能指针 draw1(Box::new(y)); draw2(&x); draw2(&y); }
- draw1 函数的参数是 Box 形式的特征对象,该特征对象是通过 Box::new(x) 的方式创建的
- draw2 函数的参数是 &dyn Draw 形式的特征对象,该特征对象是通过 &x 的方式创建的
- dyn 关键字只用在特征对象的类型声明上,在创建时无需使用 dyn
// 这种写法不会工作!因为最终类型匹配到的是具体类型,而非特征对象 /* pub struct Screen<T: Draw> { pub components: Vec<T>, } impl<T> Screen<T> where T: Draw { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } } */ pub struct Screen { pub components: Vec<Box<dyn Draw>>, } impl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } } pub struct Button { pub width: u32, pub height: u32, pub label: String, } impl Draw for Button { fn draw(&self) { // 绘制按钮的代码 } } struct SelectBox { width: u32, height: u32, options: Vec<String>, } impl Draw for SelectBox { fn draw(&self) { // 绘制SelectBox的代码 } } fn main() { let screen = Screen { components: vec![ Box::new(SelectBox { width: 75, height: 10, options: vec![ String::from("Yes"), String::from("Maybe"), String::from("No") ], }), Box::new(Button { width: 50, height: 10, label: String::from("OK"), }), ], }; screen.run(); }
简而言之,当类型 Button 实现了特征 Draw 时,类型 Button 的实例对象 btn 可以当作特征 Draw 的特征对象类型来使用,btn 中保存了作为特征对象的数据指针(指向类型 Button 的实例数据)和行为指针(指向 vtable)。
一定要注意,此时的 btn 是 Draw 的特征对象的实例,而不再是具体类型 Button 的实例,而且 btn 的 vtable 只包含了实现自特征 Draw 的那些方法(比如 draw),因此 btn 只能调用实现于特征 Draw 的 draw 方法,而不能调用类型 Button 本身实现的方法和类型 Button 实现于其他特征的方法。也就是说,btn 是哪个特征对象的实例,它的 vtable 中就包含了该特征的方法。
使用 dyn 返回特征
trait Bird { fn quack(&self) -> String; } struct Duck; impl Duck { fn swim(&self) { println!("Look, the duck is swimming") } } struct Swan; impl Swan { fn fly(&self) { println!("Look, the duck.. oh sorry, the swan is flying") } } impl Bird for Duck { fn quack(&self) -> String{ "duck duck".to_string() } } impl Bird for Swan { fn quack(&self) -> String{ "swan swan".to_string() } } fn main() { // 填空 let duck = Duck {}; duck.swim(); let bird = hatch_a_bird(2); // 变成鸟儿后,它忘记了如何游,因此以下代码会报错 // bird.swim(); // 但它依然可以叫唤 assert_eq!(bird.quack(), "duck duck"); let bird = hatch_a_bird(1); // 这只鸟儿忘了如何飞翔,因此以下代码会报错 // bird.fly(); // 但它也可以叫唤 assert_eq!(bird.quack(), "swan swan"); println!("Success!") } // 实现以下函数 // fn hatch_a_bird(s: usize) -> &'static dyn Bird { // if s == 1 { // &Swan {} // } else { // &Duck {} // } // } fn hatch_a_bird(s: usize) -> Box<dyn Bird> { if s == 1 { Box::new(Swan {}) } else { Box::new(Duck {}) } }
在数组中使用特征对象
trait Bird { fn quack(&self); } struct Duck; impl Duck { fn fly(&self) { println!("Look, the duck is flying") } } struct Swan; impl Swan { fn fly(&self) { println!("Look, the duck.. oh sorry, the swan is flying") } } impl Bird for Duck { fn quack(&self) { println!("{}", "duck duck"); } } impl Bird for Swan { fn quack(&self) { println!("{}", "swan swan"); } } fn main() { // 填空 // let birds : [&dyn Bird; 2]= [ // &Duck{}, // &Swan{} // ]; let birds : [Box<dyn Bird>; 2]= [ Box::new(Duck{}), Box::new(Swan{}) ]; for bird in birds { bird.quack(); // 当 duck 和 swan 变成 bird 后,它们都忘了如何翱翔于天际,只记得该怎么叫唤了。。 // 因此,以下代码会报错 // bird.fly(); } }
&dyn and Box
// 填空 trait Draw { fn draw(&self) -> String; } impl Draw for u8 { fn draw(&self) -> String { format!("u8: {}", *self) } } impl Draw for f64 { fn draw(&self) -> String { format!("f64: {}", *self) } } fn main() { let x = 1.1f64; let y = 8u8; // draw x draw_with_box(Box::new(x)); // draw y draw_with_ref(&y); println!("Success!") } fn draw_with_box(x: Box<dyn Draw>) { x.draw(); } fn draw_with_ref(x: &dyn Draw) { x.draw(); }
静态分发和动态分发Static and Dynamic dispatch
trait Foo { fn method(&self) -> String; } impl Foo for u8 { fn method(&self) -> String { format!("u8: {}", *self) } } impl Foo for String { fn method(&self) -> String { format!("string: {}", *self) } } // 通过泛型实现以下函数 fn static_dispatch(x: impl Foo) { } // 通过特征对象实现以下函数 fn dynamic_dispatch(y: &dyn Foo) { } fn main() { let x = 5u8; let y = "Hello".to_string(); static_dispatch(x); dynamic_dispatch(&y); println!("Success!") }
对象安全
一个特征能变成特征对象,首先该特征必须是对象安全的,即该特征的所有方法都必须拥有以下特点:
- 返回类型不能是 Self.
- 不能使用泛型参数
第一个是特征约束方式:
trait MyTrait { fn f(&self) -> Self; } impl MyTrait for u32 { fn f(&self) -> Self { 42 } } impl MyTrait for String { fn f(&self) -> Self { self.clone() } } fn my_function(x: impl MyTrait) -> impl MyTrait { x.f() } fn main() { let a = my_function(13_u32); my_function(String::from("abc")); }
第二个是特征对象方式:
trait MyTrait { fn f(&self) -> Box<dyn MyTrait>; } impl MyTrait for u32 { fn f(&self) -> Box<dyn MyTrait> { Box::new(42) } } impl MyTrait for String { fn f(&self) -> Box<dyn MyTrait> { Box::new(self.clone()) } } fn my_function(x: Box<dyn MyTrait>) -> Box<dyn MyTrait> { x.f() } fn main() { my_function(Box::new(13_u32)); my_function(Box::new(String::from("abc"))); }
关联类型
关联类型是在特征定义的语句块中,声明一个自定义类型,这样就可以在特征的方法签名中使用该类型:
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }
impl Iterator for Counter { type Item = u32; fn next(&mut self) -> Option<Self::Item> { // --snip-- } } fn main() { let c = Counter{..} c.next() }
结论:由于使用了泛型,导致函数头部也必须增加泛型的声明,而使用关联类型,将得到可读性好得多的代码
struct Container(i32, i32); // 使用关联类型实现重新实现以下特征 trait Contains { type A; type B; fn contains(&self, _: &Self::A, _: &Self::B) -> bool; fn first(&self) -> i32; fn last(&self) -> i32; } impl Contains for Container { type A = i32; type B = i32; fn contains(&self, number_1: &Self::A, number_2: &Self::B) -> bool { (&self.0 == number_1) && (&self.1 == number_2) } // Grab the first number. fn first(&self) -> i32 { self.0 } // Grab the last number. fn last(&self) -> i32 { self.1 } } fn difference<C: Contains>(container: &C) -> i32 { container.last() - container.first() } fn main() { let number_1 = 3; let number_2 = 10; let container = Container(number_1, number_2); println!("Does container contain {} and {}: {}", &number_1, &number_2, container.contains(&number_1, &number_2)); println!("First number: {}", container.first()); println!("Last number: {}", container.last()); println!("The difference is: {}", difference(&container)); }
默认泛型类型参数
use std::ops::Sub; #[derive(Debug, PartialEq)] struct Point<T> { x: T, y: T, } // 1 impl<T: Sub<Output = T>> Sub<Point<T>> for Point<T> { type Output = Self; fn sub(self, other: Self) -> Self::Output { Point { x: self.x - other.x, y: self.y - other.y, } } } // 2 impl<T: Sub<Output = T>> Sub<Self> for Point<T> { type Output = Self; fn sub(self, other: Self) -> Self::Output { Point { x: self.x - other.x, y: self.y - other.y, } } } // 3 impl<T: Sub<Output = T>> Sub for Point<T> { type Output = Self; fn sub(self, other: Self) -> Self::Output { Point { x: self.x - other.x, y: self.y - other.y, } } } fn main() { assert_eq!(Point { x: 2, y: 3 } - Point { x: 1, y: 0 }, Point { x: 1, y: 3 }); println!("Success!") }
完全限定语法
调用同名的方法
trait Pilot { fn fly(&self); } trait Wizard { fn fly(&self); } struct Human; impl Pilot for Human { fn fly(&self) { println!("This is your captain speaking."); } } impl Wizard for Human { fn fly(&self) { println!("Up!"); } } impl Human { fn fly(&self) { println!("*waving arms furiously*"); } } fn main() { let person = Human; Pilot::fly(&person); // 调用Pilot特征上的方法 Wizard::fly(&person); // 调用Wizard特征上的方法 person.fly(); // 调用Human类型自身的方法 }
trait UsernameWidget { fn get(&self) -> String; } trait AgeWidget { fn get(&self) -> u8; } struct Form { username: String, age: u8, } impl UsernameWidget for Form { fn get(&self) -> String { self.username.clone() } } impl AgeWidget for Form { fn get(&self) -> u8 { self.age } } fn main() { let form = Form{ username: "rustacean".to_owned(), age: 28, }; // 如果你反注释下面一行代码,将看到一个错误: Fully Qualified Syntax // 毕竟,这里有好几个同名的 `get` 方法 // // println!("{}", form.get()); let username = UsernameWidget::get(&form); assert_eq!("rustacean".to_owned(), username); let age = AgeWidget::get(&form); // 你还可以使用以下语法 `<Form as AgeWidget>::get` assert_eq!(28, age); println!("Success!") }
关联函数同名
trait Animal { fn baby_name() -> String; } struct Dog; impl Dog { fn baby_name() -> String { String::from("Spot") } } impl Animal for Dog { fn baby_name() -> String { String::from("puppy") } } // 错 // fn main() { // println!("A baby dog is called a {}", Dog::baby_name()); // } fn main() { println!("A baby dog is called a {}", <Dog as Animal>::baby_name()); }
完全限定语法定义为:
<Type as Trait>::function(receiver_if_method, next_arg, ...);
特征定义中的特征约束
Supertraits
有些时候我们希望在特征上实现类似继承的特性,例如让一个特征 A 使用另一个特征 B 的功能。这种情况下,一个类型要实现特征 A 首先要实现特征 B, 特征 B 就被称为 supertrait
use std::fmt::Display; trait OutlinePrint: 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)); } } use std::fmt; struct Point { x: i32, y: i32, } impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } impl OutlinePrint for Point {}
use std::fmt::Debug; trait Person: Debug { fn name(&self) -> String; } // Person 是 Student 的 supertrait . // 实现 Student 需要同时实现 Person. trait Student: Person { fn university(&self) -> String; } trait Programmer { fn fav_language(&self) -> String; } // CompSciStudent (computer science student) 是 Programmer // 和 Student 的 subtrait. 实现 CompSciStudent 需要先实现这两个 supertraits. trait CompSciStudent: Programmer + Student { fn git_username(&self) -> String; } fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String { format!( "My name is {} and I attend {}. My favorite language is {}. My Git username is {}", student.name(), student.university(), student.fav_language(), student.git_username() ) } #[derive(Debug)] struct CSStudent { name: String, university: String, fav_language: String, git_username: String } impl Person for CSStudent { fn name(&self) -> String { self.name.clone() } } impl Student for CSStudent { fn university(&self) -> String { self.university.clone() } } impl Programmer for CSStudent { fn fav_language(&self) -> String { self.fav_language.clone() } } // 为 CSStudent 实现所需的特征 impl CompSciStudent for CSStudent { fn git_username(&self) -> String { self.git_username.clone() } } fn main() { let student = CSStudent { name: "Sunfei".to_string(), university: "XXX".to_string(), fav_language: "Rust".to_string(), git_username: "sunface".to_string() }; // 填空 println!("{}", comp_sci_student_greeting(&student)); println!("{:?}", student); }
在外部类型上实现外部特征(newtype)
这里提供一个办法来绕过孤儿规则(就是特征或者类型必需至少有一个是本地的),那就是使用newtype 模式,简而言之:就是为一个元组结构体创建新类型。该元组结构体封装有一个字段,该字段就是希望实现特征的具体类型。
👇 想给 Vec<String>
加上 Display
特征,但这是个孤儿规则,因为两个定义都不在本地,可以使用newtype技巧。
use std::fmt; struct Wrapper(Vec<String>); impl fmt::Display for Wrapper { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[{}]", self.0.join(", ")) } } fn main() { let w = Wrapper(vec![String::from("hello"), String::from("world")]); println!("w = {}", w); }
use std::fmt; // 定义一个 newtype `Pretty` struct Pretty(String); impl fmt::Display for Pretty { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "\"{}\"", self.0.clone() + ", world") } } fn main() { let w = Pretty("hello".to_string()); println!("w = {}", w); }