【问题标题】:Transform static dispatch code into dynamic dispatch将静态调度代码转换为动态调度
【发布时间】:2021-05-08 07:42:07
【问题描述】:

在rust lang book 的第 10 章中有一个静态调度示例,如下所示:

fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {
    let mut largest = list[0];
    for &i in list {
        if i > largest {
            largest = i;
        }
    }
    largest
}

我正在尝试使用动态调度 as described in chapter 17 重新实现相同的示例。迄今为止我最好的尝试:

fn largest_dyn(list: &[Box<dyn PartialOrd<i32>>]) -> Box<dyn PartialOrd<i32>> {
    let mut largest = &list[0];
    for &i in list {
        if *i > **largest {
            **largest = *i;
        }
    }
    **largest
}

但这不能编译。我得到的错误是:

expected `i32`, found trait object `dyn PartialOrd`

我有很多问题希望有人能提供帮助:

  1. 为什么上面没有编译?似乎我取消引用左侧和右侧的次数相同,但一个是i32,另一个是dyn trait object?
  2. 编译器强迫我将&lt;i32&gt; 放在dyn PartialOrd 之后,以达到这一点。我不明白 - 特征对象的目的不是特别是它们不需要您指定类型,而只需要指定特征吗?为什么我要在这里输入类型?
  3. 原来的函数实际上实现了两个特征:PartialOrd和Copy。我找不到使用Box&lt;&gt; 的方法 - Box&lt;dyn PartialOrd + dyn Copy&gt; 似乎不起作用。有没有办法同时为多个特征创建一个特征对象?

【问题讨论】:

    标签: rust


    【解决方案1】:
    1. 为什么上面没有编译?似乎我取消引用左侧和右侧的次数相同,但一个是i32,另一个是dyn trait object?

    双方(最多)取消引用一个特征对象,并且这两个特征对象都实现PartialOrd&lt;i32&gt;,通过它可以将它们各自与i32类型的值进行比较。比较尝试使用左侧操作数的PartialOrd 实现;它不编译的原因是右手操作数不是i32类型的值,它是一个特征对象!

    如果您考虑一下,这完全有道理。你有两个 trait 对象,每个都包含两个指针:一个指向一个对象(Rust 不知道其类型),另一个指向该对象的 PartialOrd trait 的 vtable,以便 Rust 可以为该对象调用该 trait 的方法。 PartialOrd 提供了 partial_cmp 方法,通过该方法可以将对象与值进行比较...但是如果您只有一个指向未知类型对象的指针和一个指向另一个 vtable 的指针,那么如何实现比较?

    1. 编译器强迫我将&lt;i32&gt; 放在dyn PartialOrd 之后,以达到这一点。我不明白 - 特征对象的目的不是特别是它们不需要您指定类型,而只需要指定特征吗?为什么我要在这里输入类型?

    要创建一个 trait 对象,该 trait 对象的每个实例都必须具有完全相同的 vtable 形状:因此所有对象的任何类型参数或关联类型都必须相同,编译器通过要求这些类型是在 trait 对象的类型中明确提供。

    当然,许多 trait 没有类型参数或关联类型——它们仍然可以非常有用地被制成 trait 对象(受制于对象安全的通常规则)。但即使一个 trait 确实有这样的类型,我们也可以对它们进行泛型化(这样 Rust 会为每个实例化泛型参数的类型组合创建不同类型的 trait 对象);例如:

    fn foo<T>(list: &[Box<dyn SomeTrait<T>>])
    
    1. 原来的函数实际上实现了两个特征:PartialOrd和Copy。我找不到使用Box&lt;&gt; 的方法 - Box&lt;dyn PartialOrd + dyn Copy&gt; 似乎不起作用。有没有办法同时为多个特征创建一个特征对象?

    您只能有一个非自动特征,但您可以指定其他自动特征(例如Send 和Sync):Box&lt;dyn PartialOrd&lt;T&gt; + Send + Sync&gt;。在这种情况下,PartialOrd 和 Copy 都是非自动特征,因此不能在两者上创建特征对象。

    【讨论】:

    • 感谢@eggyal 的回复。 1 和 3 有意义,但 2 令人困惑。我的理解是,特征安全的要求之一是特征对象不能有任何泛型。但是您的示例显示了一个带有通用 T 的特征对象。这怎么可能?
    • @ilmoi:AIUI trait 对象不能有未指定或无约束的泛型类型参数(或关联类型);在指定和约束参数的地方,它是特征对象类型的一部分(因此从它的角度来看不再是“通用的”)。也许有人会纠正我,但在我看来,Rust 编程语言中的the chapter on Object Safety 在这一点上可能会更清楚一些。
    【解决方案2】:

    eggyal 的回答解释了为什么您遇到PartialOrd 的问题。然而,标准库中的某些 trait 不利于在 trait 对象中使用这一事实并不意味着您不能使用 trait 对象。您只需要创建一个更适合您的需求的 trait,而 Rust 实际上使定义您自己的 trait 变得非常容易。

    例如,要将任意值作为数字进行比较,提供一种从值中提取数字的方法可能更容易。 (这通常用于对接受排序的“关键函数”的 API 进行排序,作为“比较函数”的补充或替代。)这样的特征看起来像这样:

    trait AsInt {
        fn as_int(&self) -> i32;
    }
    

    我们可以简单地为i32 本身实现它:

    impl AsInt for i32 {
        fn as_int(&self) -> i32 {
            *self
        }
    }
    

    largest_dyn 现在看起来像这样:

    // XXX doesn't compile
    fn largest_dyn(list: &[Box<dyn AsInt>]) -> Box<dyn AsInt> {
        let mut largest = list[0];
        for i in list {
            if i.as_int() > largest.as_int() {
                largest = i;
            }
        }
        largest
    }
    

    这个实现的问题是它试图从由Box&lt;dyn ...&gt; 组成的切片中提取值。最初的实现需要T: Copy,所以它可以这样做,但是Box&lt;T&gt; 不能是Copy 而不管T,所以我们需要克隆列表中的值。对于 technical reasons,克隆 trait 对象并不像请求绑定 Clone 那样容易——相反,我们必须通过使用单独的方法扩展 trait 来提供克隆能力:

    trait AsInt {
        fn as_int(&self) -> i32;
        fn box_clone(&self) -> Box<dyn AsInt>;
    }
    
    impl AsInt for i32 {
        fn as_int(&self) -> i32 {
            *self
        }
    
        fn box_clone(&self) -> Box<dyn AsInt> {
            Box::new(*self)
        }
    }
    

    有了这个,largest_dyn() 可以是完全动态的:

    fn largest_dyn(list: &[Box<dyn AsInt>]) -> Box<dyn AsInt> {
        let mut largest = list[0].box_clone();
        for i in list {
            if i.as_int() > largest.as_int() {
                largest = i.box_clone();
            }
        }
        largest
    }
    

    Playground

    【讨论】:

    • 感谢@user4815162342 的回复。因此,我正在使用您的代码,试图使其适用于 &str 以及 i32,但我遇到了错误。这是操场链接 - play.rust-lang.org/…。你能帮我理解我做错了什么吗?
    • @ilmoi Here 是一个可以编译的修改版本。 as _ 将 Box&lt;i32&gt; 转换为 Box&lt;dyn AsInt&gt;,这很重要,因为 largest_dyn() 需要其中的一部分。 (Vec&lt;_&gt; 中的_ 告诉编译器推断类型。)您不能将字符串/整数发送到largest_dyn(),您需要将它们转换为某种形式的引用(即&amp;、Box、Rc 或 Arc)。有关更多信息,请参见例如this stream.
    • Okkkk 我明白这就是我的版本没有编译的原因。它必须是 Vec 而不是 Vec / Vec 的向量。非常感谢,非常感谢您的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-01
    • 2013-03-12
    • 1970-01-01
    • 2014-02-08
    • 1970-01-01
    • 1970-01-01
    • 2013-05-14
    相关资源
    最近更新 更多