Reputation: 262
My problem is: I have a function do_something
, which expects a vector of objects. The objects can be of different types, but they all have to implement the traits A
and B
.
I declared examples of A and B like this:
trait A: Debug {
fn say_hello_a(&self) {
println!("hello a {:?}", self);
}
}
trait B: Debug {
fn say_hello_b(&self) {
println!("hello b {:?}", self);
}
}
Now, what I want is something like that:
fn do_something(data: Vec<&dyn A + B>) {
// ...
}
However, as the dyn A + B
syntax doesn't exist in rust, I have to use a wrapper type like this:
trait Wrapper: A + B {}
fn do_something(data: Vec<&dyn Wrapper>) {
// ...
}
That seems to work. But the problem is now, in my specific problem, one of the traits, let's say A
, has to be Sized
, like this:
trait A: Debug + Sized {
fn say_hello_a(&self) {
println!("hello a {:?}", self);
}
}
trait B: Debug {
fn say_hello_b(&self) {
println!("hello b {:?}", self);
}
}
And now, the compiler throws an error at the point where Vec<&dyn Wrapper>
is the type of an argument in the function:
the trait `Wrapper` cannot be made into an object
and says it is becuase A
needs the object to be Sized
. But even if I add + Sized
to the definition of Wrapper
, it doesn't work.
So what should I do instead?
For anyone wondering: My specific problem is that I'm working on a tauri app. The function that gets the Vec as an argument is a command which should be called from the frontend. Thus, the Vec items need to implement serde::Deserialize
. But I then want to store those values in an SQLite database using rusqlite, so the Vec items should also implement rusqlite::ToSql
. And I need a way to tell my compiler the Vec items should implemenet both of these traits.
Upvotes: 0
Views: 111
Reputation: 262
Ok, as it seems like you're not able to use dyn
with Sized
traits / objects. However I find a solution that works for my original problem, maybe it also works for other usecases.
The original problem was that my function should accept arguments of every type that can be inserted in an SQL command, so everything that impements ToSql
. However, all arguments of a tauri command must be Sized
so I can't use dyn
for these arguments. So it might not be possible to accept every type of argument that implements ToSql
, but as I'm just interested in some of those types, like String
or u64
(or other integer types), I decided to create an Enum which holds the various types an argument can have:
pub enum SqlArg {
TypeStr(String),
TypeInt(u64),
TypeBool(bool),
TypeNullableStr(Option<String>),
// whatever types I need ...
}
Then, I created a function that converts a collection of SqlArg
values to a collection of &dyn ToSql
values:
trait SqlArgCollection {
fn as_params(&self) -> Vec<&dyn ToSql>;
}
impl SqlArgCollection for Vec<SqlArg> {
fn as_params(&self) -> Vec<&dyn ToSql> {
self.iter().map(|element| match element {
SqlArg::TypeInt(v) => v as &dyn ToSql,
SqlArg::TypeStr(v) => v as &dyn ToSql,
SqlArg::TypeBool(v) => v as &dyn ToSql,
SqlArg::TypeNullableStr(v) => v as &dyn ToSql,
// and so on...
}).collect::<Vec<&dyn ToSql>>()
}
}
That's a bit boilerplate because you need a match arm for every type, but that seemed to be the only solution.
However, now I can convert a Vec of SqlArg
values to a Vec of &dyn ToSql
values. For rusqlite to accept this collection, I need to convert it to a slice (but this has to be outside the as_params
function, because otherwise you'd return a reference to a temporary value. There might be a workaround by using Box
, but I'm fine with it how it is.).
#[tauri::command]
fn my_tauri_command(args: Vec<SqlArg>) {
rusqlite::Connection::open("my_database.db").unwrap()
.execute(
"insert into SomeTable values (?1, ?2, ?3);",
args.as_params().as_slice()
).unwrap();
}
And lastly, I made the SqlArg
enum untagged, so it can be build from a string or int or whatever type, and the enum variant TypeStr
, TypeInt
etc. doesn't have to be mentioned.
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub enum SqlArg {
// ...
}
Upvotes: 0
Reputation: 12094
In order to refer to a value through dynamic dispatch (via dyn Trait
syntax), the trait must be object-safe (see dyn
keyword documentation).
One requirement of object-safety is that a trait must not require implementing types to be Sized
(see object safety in traits documentation).
One way to understand why this must be true is that when you define a trait, there are potentially unlimited number of types implementing that trait, and each one is generally going to have a different size of its representation, and in fact you can't generally know all the implementing types because some other module might define another implementing type with a different size.
To solve your particular problem, I'd suggest making the Vec
hold onto a Box<dyn Wrapper>
and then it won't require Wrapper
to be Sized
.
Upvotes: 2