Rust中trait
1. 什么是trait
在Rust中,trait是一种定义共享行为的方式。它允许我们指定一个类型必须实现的方法,从而实现多态和接口抽象。
下面是一个简单的例子,定义了一个名为Printable
的trait,它包含一个名为print
的方法:
trait Printable {
fn print(&self);
}
2. trait的定义和实现
要定义一个trait,我们使用trait
关键字,后跟trait的名称和一对大括号。在大括号内,我们可以定义该trait包含的方法。
要实现一个trait,我们使用impl
关键字,后跟trait的名称和for
关键字,然后指定要为其实现该trait的类型。在大括号内,我们需要为该类型实现trait中定义的所有方法。
下面是一个例子,展示了如何为i32
类型实现上面定义的Printable
trait:
impl Printable for i32 {
fn print(&self) {
println!("{}", self);
}
}
在这个例子中,我们为i32
类型实现了Printable
trait,并为其提供了一个简单的print
方法实现。
3. trait的继承和组合
Rust允许我们通过继承和组合来扩展已有的trait。继承允许我们在新的trait中重用父trait中定义的方法,而组合则允许我们在新的trait中使用多个不同的trait。
下面是一个例子,展示了如何使用继承来扩展上面定义的Printable
trait:
trait PrintableWithLabel: Printable {
fn print_with_label(&self, label: &str) {
print!("{}: ", label);
self.print();
}
}
在这个例子中,我们定义了一个新的trait PrintableWithLabel
,它继承自 Printable
trait。这意味着任何实现了 PrintableWithLabel
的类型也必须实现 Printable
trait。此外,我们还为 PrintableWithLabel
提供了一个新方法 print_with_label
,它可以在打印值之前先打印一个标签。
下面是一个例子,展示了如何使用组合来定义一个新的trait:
trait DisplayAndDebug: Display + Debug {}
在这个例子中,我们定义了一个新的trait DisplayAndDebug
,它由两个标准库中的trait Display
和 Debug
组成。这意味着任何实现了 DisplayAndDebug
的类型也必须同时实现 Display
和 Debug
trait。
4. trait作为参数和返回值
Rust允许我们在函数签名中使用trait作为参数和返回值。这样,我们就可以编写更加通用和灵活的代码。
下面是一个例子,展示了如何使用上面定义的 PrintableWithLabel
trait作为函数参数:
fn print_twice<T: PrintableWithLabel>(value: T) {
value.print_with_label("First");
value.print_with_label("Second");
}
在这个例子中,我们定义了一个名为 print_twice
的函数,它接受一个泛型参数 T
。该参数必须实现 PrintableWithLabel
trait。然后,在函数体内部,我们可以调用该参数上的 print_with_label
方法。
下面是一个例子,展示了如何使用trait作为函数返回值:
fn get_printable() -> impl Printable {
42
}
fn get_printable() -> impl Printable { 42 }
这段代码是不正确的,因为 42
是一个整数,它并没有实现 Printable
trait。
正确的做法是返回一个实现了 Printable
trait 的类型。例如,如果我们为 i32
类型实现了 Printable
trait,那么我们可以这样写:
impl Printable for i32 {
fn print(&self) {
println!("{}", self);
}
}
fn get_printable() -> impl Printable {
42
}
在这个例子中,我们为 i32
类型实现了 Printable
trait,并提供了一个简单的 print
方法实现。然后,在 get_printable
函数中,我们返回了一个 i32
类型的值 42
。由于 i32
类型实现了 Printable
trait,所以这段代码是正确的。
在这个例子中,我们定义了一个名为 get_printable
的函数,它返回一个
5. trait对象和静态分发
在Rust中,我们可以使用两种不同的方式来实现多态:静态分发和动态分发。
静态分发是通过泛型来实现的。当我们使用泛型参数时,编译器会为每种可能的类型生成单独的代码。这样,我们就可以在编译时确定调用哪个方法。
动态分发则是通过trait对象来实现的。当我们使用trait对象时,编译器会生成一份通用的代码,它可以处理任何实现了该trait的类型。这样,我们就可以在运行时确定调用哪个方法。
下面是一个例子,展示了如何使用静态分发和动态分发来实现多态:
fn print_static<T: Printable>(value: T) {
value.print();
}
fn print_dynamic(value: &dyn Printable) {
value.print();
}
在这个例子中,我们定义了两个函数:print_static
和 print_dynamic
。print_static
函数使用泛型参数 T
,它必须实现 Printable
trait。这样,当我们调用该函数时,编译器会为每种可能的类型生成单独的代码。
print_dynamic
函数则使用了一个trait对象 &dyn Printable
作为参数。这样,当我们调用该函数时,编译器会生成一份通用的代码,它可以处理任何实现了 Printable
trait的类型。
6. 关联类型和泛型约束
在Rust中,我们可以使用关联类型和泛型约束来定义更加复杂的trait。
关联类型允许我们在trait中定义一个与其它类型相关联的类型。这样,我们就可以在trait中定义一些依赖于关联类型的方法。
下面是一个例子,展示了如何使用关联类型来定义一个名为 Add
的trait:
trait Add<RHS = Self> {
type Output;
fn add(self, rhs: RHS) -> Self::Output;
}
在这个例子中,我们定义了一个名为 Add
的trait,它包含一个关联类型 Output
和一个方法 add
。该方法接受一个泛型参数 RHS
,它默认为 Self
类型。然后,该方法返回一个 Self::Output
类型的值。
泛型约束允许我们指定泛型参数必须满足的条件。例如,我们可以指定一个泛型参数必须实现某个特定的trait。
下面是一个例子,展示了如何使用泛型约束来定义一个名为 SummableIterator
的trait:
use std::iter::Sum;
trait SummableIterator: Iterator
where
Self::Item: Sum,
{
fn sum(self) -> Self::Item {
self.fold(Self::Item::zero(), |acc, x| acc + x)
}
}
在这个例子中,我们定义了一个名为 SummableIterator
的trait,它继承自标准库中的 Iterator
trait。此外,我们还指定了一个泛型约束:该trait的关联类型 Item
必须实现标准库中的 Sum
trait。然后,在该trait中,我们定义了一个名为 sum
的方法,它可以计算迭代器中所有元素的总和。
7. 实例:使用trait实现多态
下面是一个例子,展示了如何使用上面定义的 PrintableWithLabel
trait来实现多态:
struct Circle {
radius: f64,
}
impl Printable for Circle {
fn print(&self) {
println!("Circle with radius {}", self.radius);
}
}
impl PrintableWithLabel for Circle {}
struct Square {
side: f64,
}
impl Printable for Square {
fn print(&self) {
println!("Square with side {}", self.side);
}
}
impl PrintableWithLabel for Square {}
fn main() {
let shapes: Vec<Box<dyn PrintableWithLabel>> = vec![
Box::new(Circle { radius: 1.0 }),
Box::new(Square { side: 2.0 }),
];
for shape in shapes {
shape.print_with_label("Shape");
}
}
在这个例子中,我们定义了两个结构体:Circle
和 Square
。然后,我们为这两个结构体分别实现了 Printable
和 PrintableWithLabel
trait。
在 main
函数中,我们创建了一个名为 shapes
的向量,它包含了两个trait对象:一个 Circle
实例和一个 Square
实例。然后,我们遍历这个向量,并调用每个元素上的 print_with_label
方法。
由于 Circle
和 Square
都实现了 PrintableWithLabel
trait,所以它们都可以作为trait对象存储在向量中。当我们调用它们上面的 print_with_label
方法时,编译器会根据它们的实际类型来确定调用哪个方法。
这就是如何使用trait来实现多态的一个简单例子。希望这篇文章能够帮助你更好地理解Rust中的trait。from刘金,转载请注明原文链接。感谢!
转载自:https://juejin.cn/post/7225623397145002044