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类型完成上面界说的Printabletrait:

impl Printable for i32 {
    fn print(&self) {
        println!("{}", self);
    }
}

在这个比如中,咱们为i32类型完成了Printabletrait,并为其供给了一个简略的print办法完成。

3. trait的承继和组合

Rust答应咱们经过承继和组合来扩展已有的trait。承继答应咱们在新的trait中重用父trait中界说的办法,而组合则答应咱们在新的trait中运用多个不同的trait。

下面是一个比如,展现了怎么运用承继来扩展上面界说的Printabletrait:

trait PrintableWithLabel: Printable {
    fn print_with_label(&self, label: &str) {
        print!("{}: ", label);
        self.print();
    }
}

在这个比如中,咱们界说了一个新的traitPrintableWithLabel,它承继自Printabletrait。这意味着任何完成了PrintableWithLabel的类型也有必要完成Printabletrait。此外,咱们还为PrintableWithLabel供给了一个新办法print_with_label,它能够在打印值之前先打印一个标签。

下面是一个比如,展现了怎么运用组合来界说一个新的trait:

trait DisplayAndDebug: Display + Debug {}

在这个比如中,咱们界说了一个新的traitDisplayAndDebug,它由两个规范库中的traitDisplayDebug组成。这意味着任何完成了DisplayAndDebug的类型也有必要一起完成DisplayDebugtrait。

4. trait作为参数和回来值

Rust答应咱们在函数签名中运用trait作为参数和回来值。这样,咱们就能够编写更加通用和灵敏的代码。

下面是一个比如,展现了怎么运用上面界说的PrintableWithLabeltrait作为函数参数:

fn print_twice<T: PrintableWithLabel>(value: T) {
    value.print_with_label("First");
    value.print_with_label("Second");
}

在这个比如中,咱们界说了一个名为print_twice的函数,它承受一个泛型参数T。该参数有必要完成PrintableWithLabeltrait。然后,在函数体内部,咱们能够调用该参数上的print_with_label办法。

下面是一个比如,展现了怎么运用trait作为函数回来值:

fn get_printable() -> impl Printable {
    42
}

fn get_printable() -> impl Printable { 42 }这段代码是不正确的,因为42是一个整数,它并没有完成Printabletrait。

正确的做法是回来一个完成了Printabletrait 的类型。例如,如果咱们为i32类型完成了Printabletrait,那么咱们能够这样写:

impl Printable for i32 {
    fn print(&self) {
        println!("{}", self);
    }
}
fn get_printable() -> impl Printable {
    42
}

在这个比如中,咱们为i32类型完成了Printabletrait,并供给了一个简略的print办法完成。然后,在get_printable函数中,咱们回来了一个i32类型的值42。因为i32类型完成了Printabletrait,所以这段代码是正确的。

在这个比如中,咱们界说了一个名为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_staticprint_dynamicprint_static函数运用泛型参数T,它有必要完成Printabletrait。这样,当咱们调用该函数时,编译器会为每种可能的类型生成单独的代码。

print_dynamic函数则运用了一个trait目标&dyn Printable作为参数。这样,当咱们调用该函数时,编译器会生成一份通用的代码,它能够处理任何完成了Printabletrait的类型。

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,它承继自规范库中的Iteratortrait。此外,咱们还指定了一个泛型束缚:该trait的相关类型Item有必要完成规范库中的Sumtrait。然后,在该trait中,咱们界说了一个名为sum的办法,它能够核算迭代器中一切元素的总和。

7. 实例:运用trait完成多态

下面是一个比如,展现了怎么运用上面界说的PrintableWithLabeltrait来完成多态:

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");
    }
}

在这个比如中,咱们界说了两个结构体:CircleSquare。然后,咱们为这两个结构体别离完成了PrintablePrintableWithLabeltrait。

main函数中,咱们创建了一个名为shapes的向量,它包括了两个trait目标:一个Circle实例和一个Square实例。然后,咱们遍历这个向量,并调用每个元素上的print_with_label办法。

因为CircleSquare都完成了PrintableWithLabeltrait,所以它们都能够作为trait目标存储在向量中。当咱们调用它们上面的print_with_label办法时,编译器会根据它们的实践类型来确认调用哪个办法。

这便是怎么运用trait来完成多态的一个简略比如。希望这篇文章能够帮助你更好地了解Rust中的trait。from刘金,转载请注明原文链接。感谢!