Rust traits,包括自定义trait声明,trait边界,实现trait的返回类型,条件方法实现和blanket实现。Rust的多态性严重依赖于traits,这允许基于trait的分派和泛型编程。掌握traits使开发人员能够创建灵活的、可维护的代码,具有低运行时开销和可靠的编译时保证。
Introduction 介绍
Traits in Rust define a set of behaviors that types can implement, enabling polymorphism and code reusability, Yesterday we looked at the PartialOrd
trait. Well we can create custom traits and implement them for structs…
Rust 中的 taits 定义了一组类型可以实现的行为,从而实现多态性和代码可重用性。昨天我们研究了 PartialOrd 特征。 好吧,我们可以创建自定义特征并为结构实现它们......
We can use the trait
keyword to define a trait
我们可以使用 Trait 关键字来定义特征
Let’s take a look at an example
我们来看一个例子
// Define a trait named 'Sound' with a method 'make_sound'.
trait Sound {
fn make_sound(&self);
}
// Implement the 'Sound' trait for the type 'Dog'.
struct Dog;
impl Sound for Dog {
fn make_sound(&self) {
println!("Woof!");
}
}
// Implement the 'Sound' trait for the type 'Cat'.
struct Cat;
impl Sound for Cat {
fn make_sound(&self) {
println!("Meow!");
}
}
// A function that takes any type implementing the 'Sound' trait and makes it produce a sound.
fn animal_sound<T: Sound>(animal: T) {
animal.make_sound();
}
fn main() {
let dog = Dog;
let cat = Cat;
// Call the 'animal_sound' function with different types.
animal_sound(dog);
animal_sound(cat);
}
- We define a trait named
Sound
with a methodmake_sound
.
我们使用 make_sound 方法定义一个名为 Sound 的特征。
- We implement the
Sound
trait for the typesDog
andCat
.
我们为狗和猫类型实现了声音特征。
- We define a function
animal_sound
that takes any type implementing theSound
trait and makes it produce a sound.
我们定义一个函数animal_sound,它接受任何实现声音特征的类型并使其产生声音。
- In the
main
function, we create instances ofDog
andCat
, and then we callanimal_sound
with both of these instances.
在主函数中,我们创建 Dog 和 Cat 的实例,然后使用这两个实例调用animal_sound。
We had to declare how we wanted to use the sound function for each structure, but we can also specify default cases by doing something like this
我们必须声明如何为每个结构使用声音函数,但我们也可以通过执行以下操作来指定默认情况
// Define a trait named 'Sound' with a method 'make_sound'.
trait Sound {
//Default implementation
fn make_sound(&self){
println!("This is a default implementation");
}
}
// Implement the 'Sound' trait for the type 'Dog'.
struct Dog;
impl Sound for Dog {
//overridden
fn make_sound(&self) {
println!("Woof!");
}
}
// Implement the 'Sound' trait for the type 'Cat'.
struct Cat;
impl Sound for Cat {
//overridden
fn make_sound(&self) {
println!("Meow!");
}
}
//uses the default implementation
struct Elephant;
impl Sound for Elephant{}
// A function that takes any type implementing the 'Sound' trait and makes it produce a sound.
fn animal_sound<T: Sound>(animal: T) {
animal.make_sound();
}
fn main() {
let dog = Dog;
let cat = Cat;
let elephant = Elephant;
// Call the 'animal_sound' function with different types.
animal_sound(dog);
animal_sound(cat);
animal_sound(elephant);
}
Output : 输出量:
Woof!
Meow!
This is a default implementation
Trait bounds 特质界限
In Rust, trait bounds are used to restrict generic types to types that implement certain traits. This ensures that the generic code can only be used with types that support the behavior defined by those traits. Traits can also be used directly as function arguments, allowing functions to accept any type that implements a particular trait.
在Rust中,trait bounds用于将泛型类型限制为实现某些trait的类型。这确保泛型代码只能与支持这些trait定义的行为的类型一起使用。trait也可以直接用作函数参数,允许函数接受实现特定trait的任何类型。
We saw this with yesterday’s find_max
function
我们在昨天的 find_max
函数中看到了这一点
fn find_max<T: PartialOrd>(x: T, y: T) -> T{
if x > y {
x
}else{
y
}
}
Here <T: PartialOrd>
is the specified trait bound…
这里 <T: PartialOrd>
是指定的trait绑定.
Above, in the animal_sound
function we have used a similar ideology fn animal_sound<T: Sound>(animal: T)
in this line
在上面的 animal_sound
函数中,我们在这一行中使用了类似的思想 fn animal_sound<T: Sound>(animal: T)
fn animal_sound<T: Sound>(animal: T) {
animal.make_sound();
}
This functon can also be declared as follows :
这个函数也可以声明如下:
fn animal_sound(animal: &impl Sound) {
animal.make_sound();
}
Returning types that implement traits
返回实现trait的类型
We can do basically the same thing to return types through functions
我们基本上可以做同样的事情来通过函数返回类型
trait Sound {
//Default implementation
fn make_sound(&self){
println!("This is a default implementation");
}
}
// Implement the 'Sound' trait for the type 'Dog'.
struct Dog;
impl Sound for Dog {
//overridden
fn make_sound(&self) {
println!("Woof!");
}
}
// Implement the 'Sound' trait for the type 'Cat'.
struct Cat;
impl Sound for Cat {
//overridden
fn make_sound(&self) {
println!("Meow!");
}
}
fn return_animal(name: &str) -> Box<dyn Sound>{
match name{
"dog" => Box::new(Dog),
"cat" => Box::new(Cat),
_ => panic!("Unsupported animal type"),
}
}
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>
:
- This function takes a string
name
and returns a boxed trait object implementing theSound
trait.
这个函数接受一个字符串name
,并返回一个实现Sound
trait的盒装trait对象。 - It creates and returns a boxed instance of either
Dog
orCat
based on the value ofname
.
它根据name
的值创建并返回Dog
或Cat
的装箱实例。 - The
Box
is used for dynamic memory allocation on the heap.Box
用于堆上的动态内存分配。 - If we don’t use
Box
, we will get an error that looks like this :
如果我们不使用Box
,我们将得到一个错误,看起来像这样:
`match` arms have incompatible types
- If the provided
name
doesn't match"dog"
or"cat"
, it panics with an error message.
如果提供的name
与"dog"
或"cat"
不匹配,则会出现死机并显示错误消息。
fn return_cat() -> impl Sound
:
- This function returns any instance that implements the Sound trait, as of now we are returning a
Cat
type through this function.
这个函数返回任何实现Sound trait的实例,到目前为止,我们通过这个函数返回Cat
类型。
Output: 输出量:
Woof!
Meow!
Conditionally implementing methods
实现方法
If you take a look at yesterday’s article, We wrote this code:
如果你看看昨天的文章,我们写了这段代码:
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 f32
println!("X: {}, Y: {}",point2.x(),point2.y);
}
Here we have two implementation blocks, One for a generic type which will return the X
co-ordinate for every Point but the other implementation block only works for signed 32 bit integers. For us to get the y
co-ordinate of a Point both the values of the struct will have to be a signed 32 bit integer
这里我们有两个实现块,一个用于泛型类型,它将返回每个Point的 X
坐标,但另一个实现块仅适用于有符号的32位整数。对于我们来说,要获得Point的 y
坐标,结构体的两个值都必须是有符号的32位整数
so here, I can get the x and y co-ordinate of point1
but only the x co-ordinate of point2
using methods
所以在这里,我可以得到 point1
的x和y坐标,但只能得到 point2
的x坐标,使用方法
Output: 输出量:
X: 3, Y: 10
X: 3.4, Y: 6.9
Blanket implementations 一揽子实施
Blanket implementations in Rust allow you to implement traits for all types that meet certain criteria, providing a default implementation for a trait across multiple types at once.
Rust中的Blanket实现允许您为满足某些条件的所有类型实现trait,同时为多个类型的trait提供默认实现。
use std::fmt::{Debug, Display};
// Define a generic function that displays the value.
fn display_value<T: Debug + Display>(value: T) {
println!("Value: {}", value);
}
fn main() {
let number = 42;
let text = "Hello, Rust!";
// Call the 'display_value' function with different types.
display_value(number); // Output: Value: 42
display_value(text); // Output: Value: Hello, Rust!
}
- We define a generic function
display_value
that takes any typeT
that implements both theDebug
andDisplay
traits.
我们定义了一个泛型函数display_value
,它接受任何实现Debug
和Display
trait的类型T
。 - Rust’s standard library provides a blanket implementation of the
Display
trait for any type that implementsDebug
, allowing us to usedisplay_value
with types likei32
and&str
directly.
Rust的标准库为任何实现Debug
的类型提供了Display
trait的全面实现,允许我们直接将display_value
与i32
和&str
等类型一起使用。 - When
display_value
is called withnumber
(ani32
) andtext
(a&str
), it successfully displays their values using theDisplay
implementation provided by theDebug
trait.
当使用number
(一个i32
)和text
(一个&str
)调用display_value
时,它会使用Debug
trait提供的Display
实现成功显示它们的值。