大家好,
欢迎来到学习Rust的第17天,今天我们将研究Rust编程语言中的trait。
如果你还没有阅读昨天的文章,我建议你去读一下,因为我们今天学习的概念会建立在昨天学到的“通用类型”概念之上。
介绍
Rust中的trait定义了一组类型可以实现的行为,实现了多态性和代码可重用性。昨天我们看了PartialOrd
trait。我们可以创建自定义trait,并为结构体实现它们…
我们可以使用trait
关键字来定义一个trait。
让我们来看一个例子
// 定义一个名为'Sound'的trait,具有一个名为'make_sound'的方法。
trait Sound {fn make_sound(&self);
}// 为类型'Dog'实现'Sound' trait。
struct Dog;
impl Sound for Dog {fn make_sound(&self) {println!("汪汪!");}
}// 为类型'Cat'实现'Sound' trait。
struct Cat;
impl Sound for Cat {fn make_sound(&self) {println!("喵喵!");}
}// 一个接受任何实现了'Sound' trait的类型并让其发出声音的函数。
fn animal_sound<T: Sound>(animal: T) {animal.make_sound();
}fn main() {let dog = Dog;let cat = Cat;// 使用不同类型调用'animal_sound'函数。animal_sound(dog);animal_sound(cat);
}
- 我们定义了一个名为
Sound
的trait,具有一个名为make_sound
的方法。 - 我们为
Dog
和Cat
类型实现了Sound
trait。 - 我们定义了一个
animal_sound
函数,该函数接受任何实现了Sound
trait的类型,并使其发出声音。 - 在
main
函数中,我们创建了Dog
和Cat
的实例,然后调用了animal_sound
来使用这两个实例。
我们必须声明我们想要如何为每个结构体使用声音函数,但我们也可以通过像这样做一些默认情况来指定默认情况
// 定义一个名为'Sound'的trait,具有一个名为'make_sound'的方法。
trait Sound {// 默认实现fn make_sound(&self) {println!("这是默认实现");}
}// 为类型'Dog'实现'Sound' trait。
struct Dog;
impl Sound for Dog {// 覆盖fn make_sound(&self) {println!("汪汪!");}
}// 为类型'Cat'实现'Sound' trait。
struct Cat;
impl Sound for Cat {// 覆盖fn make_sound(&self) {println!("喵喵!");}
}// 使用默认实现
struct Elephant;
impl Sound for Elephant {}// 一个接受任何实现了'Sound' trait的类型并让其发出声音的函数。
fn animal_sound<T: Sound>(animal: T) {animal.make_sound();
}fn main() {let dog = Dog;let cat = Cat;let elephant = Elephant;// 使用不同类型调用'animal_sound'函数。animal_sound(dog);animal_sound(cat);animal_sound(elephant);
}
输出:
汪汪!
喵喵!
这是默认实现
Trait 边界
在Rust中,trait边界用于将通用类型限制为实现了特定trait的类型。这确保了通用代码只能与支持这些trait定义的行为的类型一起使用。trait还可以直接用作函数参数,允许函数接受任何实现特定trait的类型。
我们在昨天的find_max
函数中看到了这一点
fn find_max<T: PartialOrd>(x: T, y: T) -> T{if x > y {x}else{y}
}
这里的<T: PartialOrd>
是指定的trait边界…
在animal_sound
函数中,我们使用了类似的理念fn animal_sound<T: Sound>(animal: T)
,在这一行中
fn animal_sound<T: Sound>(animal: T) {animal.make_sound();
}这个函数也可以这样声明:fn animal_sound(animal: &impl Sound) {animal.make_sound();
}
返回实现trait的类型
我们也可以通过函数返回类型来做基本相同的事情
trait Sound {// 默认实现fn make_sound(&self) {println!("这是默认实现");}
}// 为类型'Dog'实现'Sound' trait。
struct Dog;
impl Sound for Dog {// 覆盖fn make_sound(&self) {println!("汪汪!");}
}// 为类型'Cat'实现'Sound' trait。
struct Cat;
impl Sound for Cat {// 覆盖fn make_sound(&self) {println!("喵喵!");}
}fn return_animal(name: &str) -> Box<dyn Sound>{match name{"dog" => Box::new(Dog),"cat" => Box::new(Cat),_ => panic!("不支持的动物类型"),}
}fn return_cat() -> impl Sound{Cat
}fn main(){let dog = return_animal("dog");let cat = return_cat();dog.make_sound();cat.make_sound();
}
return_animal(name: &str) -> Box<dyn Sound>
:
- 这个函数接受一个字符串
name
,并返回一个实现了Sound
trait的盒装trait对象。 - 它根据
name
的值创建并返回Dog
或Cat
的盒装实例。 Box
用于在堆上进行动态内存分配。- 如果我们不使用
Box
,我们将得到一个错误,看起来像这样:
match
arms have incompatible types
- 如果提供的
name
不匹配"dog"
或"cat"
,它会以错误消息抛出异常。
`fn return_cat() -> impl Sound
`:
- 这个函数返回实现Sound trait的任何实例,现在我们通过这个函数返回一个
Cat
类型。
输出:
汪汪!
喵喵!
有条件地实现方法
如果你看看昨天的文章,我们写了这段代码:
struct Point<T>{x: T,y: T,
}impl<U> Point<U>{fn x(&self) -> &U {&self.x}
}impl Point<i32>{fn y(&self) -> i32{self.y}
}fn main(){let point1 = Point{x: 3, y: 10};let point2 = Point{x:3.4, y: 6.9};println!("X: {}, Y: {}",point1.x(),point1.y());//I cannot use the y() method for point2 as its data type is f32println!("X: {}, Y: {}",point2.x(),point2.y);
}
这里我们有两个实现块,一个是用于通用类型的,它将为每个Point返回X
坐标,但另一个实现块仅对于32位有符号整数有效。要获取Point的y
坐标,结构体的值必须都是32位有符号整数。
所以在这里,我可以获得point1
的x和y坐标,但只能使用方法获取point2
的x坐标
输出:
X: 3, Y: 10
X: 3.4, Y: 6.9
全局实现
Rust中的全局实现允许您为满足某些条件的所有类型实现trait,在多个类型上为trait提供默认实现。
use std::fmt::{Debug, Display};// 定义一个显示值的通用函数。
fn display_value<T: Debug + Display>(value: T) {println!("值: {}", value);
}fn main() {let number = 42;let text = "Hello, Rust!";// 使用不同类型调用'display_value'函数。display_value(number); // 输出: 值: 42display_value(text); // 输出: 值: Hello, Rust!
}
- 我们定义了一个通用函数
display_value
,它接受任何实现了Debug
和Display
trait的类型T
。 - Rust标准库为任何实现了
Debug
的类型提供了Display
trait的全局实现,允许我们直接使用i32
和&str
等类型调用display_value
。 - 当使用
number
(一个i32
)和text
(一个&str
)调用display_value
时,它会成功地使用Debug
trait提供的Display
实现显示它们的值。
结论
最后,今天的文章简要介绍了Rust trait,包括自定义trait声明、trait边界、返回实现trait的类型、有条件的方法实现和全局实现。Rust的多态性在很大程度上依赖于trait,它允许基于trait的分发和泛型编程。掌握trait使开发人员能够创建灵活、易于维护的代码,具有低运行时开销和可靠的编译时保证。