13

在编写带有特征的代码时,您可以将特征放入特征绑定中:

use std::fmt::Debug;

fn myfunction1<T: Debug>(v: Box<T>) {
    println!("{:?}", v);
}

fn myfunction2<T: Debug>(v: &T) {
    println!("{:?}", v);
}

fn main() {
    myfunction1(Box::new(5));
    myfunction2(&5);
}

或直接在Boxor 引用类型中:

use std::fmt::Debug;

fn myfunction3(v: Box<Debug>) {
    println!("{:?}", v);
}

fn myfunction4(v: &Debug) {
    println!("{:?}", v);
}

fn main() {
    myfunction3(Box::new(5));
    myfunction4(&5);
}

这些给出相同的输出。那么区别是什么呢?

(这个问题的灵感来自另一个问题,这只是几个混合概念之一)

4

2 回答 2

11

随着<T: Trait> Box<T>您使用一个 trait bound 来告诉编译器您想要一个Box具有某种类型的实例T来实现Trait,并且您将T在使用它时指定。Rust 编译器可能会为您代码中的每个不同创建不同的、高效T的代码(单态化)。

Box<Trait>你告诉编译器你想要一个带有Box特征对象的指针,一个指向未知类型的指针Trait,这意味着编译器将使用动态调度。

我已经包含了两个示例,这使得区别更加清晰:

<T: Trait> Box<T>,即特征绑定:

use std::fmt::Debug;

struct Wrapper<T> {
    contents: Option<Box<T>>,
}

impl<T: Debug> Wrapper<T> {
    fn new() -> Wrapper<T> {
        Wrapper { contents: None }
    }

    fn insert(&mut self, val: Box<T>) {
    }
}

fn main() {
    let mut w = Wrapper::new();

    // makes T for w be an integer type, e.g. Box<i64>
    w.insert(Box::new(5));

    // type error, &str is not an integer type
    // w.insert(Box::new("hello"));
}

Box<Trait>,即特征对象:

use std::fmt::Debug;

struct Wrapper {
    contents: Option<Box<Debug>>,
}

impl Wrapper {
    fn new() -> Wrapper {
        Wrapper { contents: None }
    }

    fn insert(&mut self, val: Box<Debug>) {
    }
}

fn main() {
    let mut w = Wrapper::new();
    w.insert(Box::new(5));
    w.insert(Box::new("hello"));
}

有关 trait bounds 和 trait objects 之间区别的更多详细信息,我推荐Rust book 第一版中的 trait objects 部分

于 2017-07-17T19:02:39.117 回答
5

重要的是,您不必泛型类型放在引用后面(如&or Box),您可以直接接受它:

fn myfunction3<T: Debug>(v: T) {
    println!("{:?}", v);
}

fn main() {
    myfunction3(5);
}

这与单态化具有相同的好处,而没有额外的内存分配(Box)或需要在某处保留值的所有权()的缺点&

我想说泛型通常应该是默认选择——当存在动态调度/异构时,你只需要一个trait 对象。

于 2017-10-30T12:36:27.483 回答