How can I define a function with a parameter that

2019-06-18 16:03发布

问题:

I'm trying to define a function that will take a reference as a parameter, and call a generic method on the referenced object, passing in a concrete value. I need a way of requiring that the generic type of the parameter passed to my function is a trait of the concrete type that the function will use it with. I can't seem to work out how to do this.

A minimal example of the sort of thing I'm trying to achieve:

trait Vehicle {}
trait Floating {}

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

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

    let mut b: Vec<Box<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{}));
}

Trying to compile this gives the following errors:

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


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

I didn't expect this to compile, but I don't know how to change the signature of populate so that it will. I come from Java land, where I would achieve this using this using a bounded wildcard (e.g. void populate(List<? super Boat> receiver)), but I can't find anything to suggest that Rust offers equivalent semantics.

How might I go about fixing my definition of populate here?

I'm new to Rust, so bear with me if I'm completely barking up the wrong tree. I've searched around, and can't seem to find an example of how this pattern should be implemented.

回答1:

Stable Rust

You can create and implement a trait for every unique trait object you are interested in:

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

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

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

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

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

A macro can remove the duplication.

Nightly Rust

You can use the unstable CoerceUnsized trait:

#![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>);
}

Equivalently:

#![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>);
}

You can track their stabilization in issue 27732.

This code is only able to create a trait object, and cannot return the struct directly:

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 | |     }
   | |_____^

To work around this, you can create a trait like we did for stable Rust, but this one can have a blanket implementation for all trait objects:

#![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));
}

Thanks to aturon for pointing me to these traits and to eddyb for reminding me that traits exist!