Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

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 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>>`

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.

like image 612
user31601 Avatar asked Jul 09 '18 14:07

user31601


People also ask

How do you define a trait in Rust?

A trait in Rust is a group of methods that are defined for a particular type. Traits are an abstract definition of shared behavior amongst different types. So, in a way, traits are to Rust what interfaces are to Java or abstract classes are to C++. A trait method is able to access other methods within that trait.

What are trait bounds?

Trait and lifetime bounds provide a way for generic items to restrict which types and lifetimes are used as their parameters. Bounds can be provided on any type in a where clause.

How do you create a trait in Rust?

Implementing a trait in Rust To implement a trait, declare an impl block for the type you want to implement the trait for. The syntax is impl <trait> for <type> . You'll need to implement all the methods that don't have default implementations.

Can traits have fields Rust?

Traits can't have fields. If you want to provide access to a field from a trait, you need to define a method in that trait (like, say, get_blah ).


1 Answers

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

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!

like image 164
Shepmaster Avatar answered Oct 12 '22 08:10

Shepmaster