特征对象
特征对象指向实现了特征的类型的实例
可以通过 & 引用或者 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!")
}
对象安全
一个特征能变成特征对象,首先该特征必须是对象安全的,即该特征的所有方法都必须拥有以下特点:
第一个是特征约束方式:
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);
}