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();
}
}
在这个比如中,咱们界说了一个新的traitPrintableWithLabel
,它承继自Printable
trait。这意味着任何完成了PrintableWithLabel
的类型也有必要完成Printable
trait。此外,咱们还为PrintableWithLabel
供给了一个新办法print_with_label
,它能够在打印值之前先打印一个标签。
下面是一个比如,展现了怎么运用组合来界说一个新的trait:
trait DisplayAndDebug: Display + Debug {}
在这个比如中,咱们界说了一个新的traitDisplayAndDebug
,它由两个规范库中的traitDisplay
和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刘金,转载请注明原文链接。感谢!