user3791276
user3791276

Reputation: 79

Instantiating a struct parameterized by a trait

I'm building a webapp in Rust and trying to implement basic Rails-style database migrations for managing my database. In my code, Migration is a trait with up and down methods to apply and roll back the migration. Each individual database migration is a struct that implements the Migration trait. To keep track of database migrations in the correct order, I built a MigrationIndex class.

struct MigrationIndex<T> {
    migrations: Vec<Box<T>>
}
impl <T: Migration> MigrationIndex<T> {
    // methods for managing the migrations...
}

impl <T: Migration> Default for MigrationIndex<T> {
    pub fn default() -> MigrationIndex<T> {
        MigrationIndex {
            migrations: vec![]
        }
    }
}

So I go to use my class:

let migrations: MigrationIndex = Default::default();

But the compiler errors on this line with wrong number of type arguments: expected 1, found 0. So I tried to add the missing trait parameter:

let migrations: MigrationIndex<Migration> = Default::default();

But on that line the compiler interprets Migration as a type, not a trait, and again fails to compile. At a guess I tried:

let migrations: MigrationIndex<T: Migration> = Default::default();

but that ends up being a syntax error. Now I'm stumped. If a type is parameterized by a trait, how do I specify that trait when I instantiate it?

Upvotes: 3

Views: 460

Answers (2)

oli_obk
oli_obk

Reputation: 31273

When you use a generic, the generic argument needs to be of a single concrete type. This would cause all objects in the migrations Vec to be of the same type. From your description it does not sound like that's what you want. You want a Vec of different types that implement the same trait. This does not require generics:

#[derive(Default)]
struct MigrationIndex {
    migrations: Vec<Box<Migration>>
}
impl MigrationIndex {
    // methods for managing the migrations...
}

I also took the liberty of replacing your manual Default impl with the equivalent automatically generated one through the derive attribute.

In fact, in your previous implementation, the Box was entirely unnecessary. If you have a concrete type, you can create a Vec of elements of that type directly. Only when you want to put in different types implementing the same trait do you need the Box, because these types might have different sizes.

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 431819

When you specify a value for a generic type parameter, it has to be a concrete type, not a trait:

trait Migration {}

struct Foo;
impl Migration for Foo {}

fn main() {
    let migrations: MigrationIndex<Foo> = Default::default();
}

Upvotes: 3

Related Questions