Reputation: 699
I'm working on a SQL migration tool. Right now I only support Postgresql but i'd like to add Mysql, etc.
I have the following trait that drivers need to implement:
pub trait Driver {
fn ensure_migration_table_exists(&self);
fn remove_migration_table(&self);
fn get_current_number(&self) -> i32;
fn set_current_number(&self, number: i32);
fn migrate(&self, migration: String, number: i32) -> MigrateResult<()>;
}
I want to make a function get_driver
which would have the following conceptual definition fn get_driver(url: &str) -> MigrateResult<Driver>
.
Based on a discussion on IRC a month ago, this is apparently impossible to do. My previous best guess fails like so:
fn get_driver<T: Driver + Sized>(url: &str) -> MigrateResult<T>
expected `core::result::Result<T, errors::MigrateError>`,
found `core::result::Result<drivers::pg::Postgres, errors::MigrateError>`
(expected type parameter,
found struct drivers::pg::Postgres
) [E0308]
Is there any way to work around that?
Upvotes: 0
Views: 926
Reputation: 10180
In this function:
fn get_driver<T: Driver + Sized>(url: &str) -> MigrateResult<T>
T
is a type parameter. It is chosen by the caller of the function.
It looks like you want to a return a different type based on the value of url
. But Rust is statically-typed: the type of any expression at any point in the code needs to be known at compile-time. (Even generics get specialized to concrete types when they’re used.) The way to deal with this is to use trait objects:
For example, in:
fn get_driver(url: &str) -> MigrateResult<Box<Driver>>
Box<Driver>
is a fat pointer made of a pointer to the value and a pointer to the virtual call table for the a concrete type that implements the Driver
trait. That vtable contains pointers to the code for each method of the trait.
Read more at http://doc.rust-lang.org/book/trait-objects.html
Upvotes: 3