如何定义一个参数可以是多种特征对象的函数?

How can I define a function with a parameter that can be multiple kinds of trait objects?

我正在尝试定义一个将引用作为参数的函数,并在引用的对象上调用 generic 方法,并传入具体值。我需要一种方法来要求传递给我的函数的参数的泛型类型是函数将使用它的具体类型的特征。我似乎不知道该怎么做。

我正在努力实现的那种事情的最小示例:

trait Vehicle {}
trait Floating {}

struct Boat;
impl Vehicle for Boat {}
impl Floating for Boat {}

fn main() {
    let mut a: Vec<Box<dyn Vehicle>> = vec![];
    populate(&mut a); // Does not compile

    let mut b: Vec<Box<dyn Floating>> = vec![];
    populate(&mut b); // Also does not compile
}

fn populate(receiver: &mut Vec<Box<Boat>>) { // What should I put here?
    receiver.push(Box::new(Boat{}));
}

尝试编译它会出现以下错误:

error[E0308]: mismatched types
  --> src/main.rs:10:14
   |
10 |     populate(&mut a); // Does not compile
   |              ^^^^^^ expected struct `Boat`, found trait object `dyn Vehicle`
   |
   = note: expected mutable reference `&mut std::vec::Vec<std::boxed::Box<Boat>>`
              found mutable reference `&mut std::vec::Vec<std::boxed::Box<dyn Vehicle>>`

error[E0308]: mismatched types
  --> src/main.rs:13:14
   |
13 |     populate(&mut b); // Also does not compile
   |              ^^^^^^ expected struct `Boat`, found trait object `dyn Floating`
   |
   = note: expected mutable reference `&mut std::vec::Vec<std::boxed::Box<Boat>>`
              found mutable reference `&mut std::vec::Vec<std::boxed::Box<dyn Floating>>`

我没想到它会编译,但我不知道如何更改 populate 的签名以便它会编译。我来自 Java 土地,在那里我会使用有界通配符(例如 void populate(List<? super Boat> receiver))来实现这一点,但我找不到任何表明 Rust 提供等效语义的东西。

我该如何修正我对 populate 的定义?

我是 Rust 的新手,如果我完全说错了树,请多多包涵。我四处搜索,但似乎无法找到应如何实施此模式的示例。

稳定锈

您可以为您感兴趣的每个独特特征对象创建和实现特征:

trait Shipyard {
    fn construct(boat: Boat) -> Box<Self>;
}

impl Shipyard for Boat {
    fn construct(boat: Boat) -> Box<Self> {
        Box::new(boat)
    }
}

impl Shipyard for dyn Vehicle {
    fn construct(boat: Boat) -> Box<dyn Vehicle> {
        Box::new(boat) as Box<dyn Vehicle>
    }
}

impl Shipyard for dyn Floating {
    fn construct(boat: Boat) -> Box<dyn Floating> {
        Box::new(boat) as Box<dyn Floating>
    }
}

fn populate<T: ?Sized>(receiver: &mut Vec<Box<T>>)
where
    T: Shipyard,
{
    receiver.push(T::construct(Boat));
}

一个宏可以去除重复。

Nightly Rust

你可以使用不稳定的CoerceUnsized特征:

#![feature(coerce_unsized)]

use std::ops::CoerceUnsized;

fn populate<T: ?Sized>(receiver: &mut Vec<Box<T>>)
where
    Box<Boat>: CoerceUnsized<Box<T>>,
{
    receiver.push(Box::new(Boat) as Box<T>);
}

等价于:

#![feature(unsize)]

use std::marker::Unsize;

fn populate<T: ?Sized>(receiver: &mut Vec<Box<T>>)
where
    Boat: Unsize<T>,
{
    receiver.push(Box::new(Boat) as Box<T>);
}

您可以在 issue 27732 中跟踪他们的稳定性。

这段代码只能创建特征对象,不能直接return结构:

let mut b: Vec<Box<Boat>> = vec![];
populate(&mut b);
error[E0277]: the trait bound `Boat: std::marker::Unsize<Boat>` is not satisfied
  --> src/main.rs:17:5
   |
17 |     populate(&mut b);
   |     ^^^^^^^^ the trait `std::marker::Unsize<Boat>` is not implemented for `Boat`
   |
   = note: required because of the requirements on the impl of `std::ops::CoerceUnsized<std::boxed::Box<Boat>>` for `std::boxed::Box<Boat>`
note: required by `populate`
  --> src/main.rs:25:5
   |
25 | /     fn populate<T: ?Sized>(receiver: &mut Vec<Box<T>>)
26 | |     where
27 | |         Box<Boat>: CoerceUnsized<Box<T>>,
28 | |     {
29 | |         receiver.push(Box::new(Boat) as Box<T>);
30 | |     }
   | |_____^

要解决这个问题,您可以像我们为稳定的 Rust 所做的那样创建一个特征,但是这个可以对所有特征对象进行全面实现:

#![feature(unsize)]

use std::marker::Unsize;

trait Shipyard {
    fn construct(boat: Boat) -> Box<Self>;
}

impl Shipyard for Boat {
    fn construct(boat: Boat) -> Box<Self> {
        Box::new(boat)
    }
}

impl<U: ?Sized> Shipyard for U
where
    Boat: Unsize<U>,
{
    fn construct(boat: Boat) -> Box<Self> {
        Box::new(boat) as Box<U>
    }
}

fn populate<T: ?Sized>(receiver: &mut Vec<Box<T>>)
where
    T: Shipyard,
{
    receiver.push(T::construct(Boat));
}

感谢aturon for pointing me to these traits and to eddyb for reminding me that traits exist