Reputation: 35216
I'm trying to implement this pattern:
use std::any::Any;
use std::fmt::Debug;
trait CommandHandler<TCommand> {
fn execute(&self, data: TCommand);
}
#[derive(Debug)]
struct FooCommand {}
struct FooCommandHandler {}
impl CommandHandler<FooCommand> for FooCommandHandler {
fn execute(&self, data: FooCommand) {
println!("Foo");
}
}
#[derive(Debug)]
struct BarCommand {}
struct BarCommandHandler {}
impl CommandHandler<BarCommand> for BarCommandHandler {
fn execute(&self, data: BarCommand) {
println!("Bar");
}
}
fn execute<T>(command: T)
where
T: Any + Debug,
{
println!("Command: {:?}", command);
match (&command as &Any).downcast_ref::<FooCommand>() {
Some(c) => (FooCommandHandler {}).execute(c),
None => {}
};
match (&command as &Any).downcast_ref::<BarCommand>() {
Some(c) => (BarCommandHandler {}).execute(c),
None => {}
};
}
fn main() {
(FooCommandHandler {}).execute(FooCommand {});
(BarCommandHandler {}).execute(BarCommand {});
execute(FooCommand {});
execute(BarCommand {});
}
This doesn't work:
error[E0308]: mismatched types
--> src/main.rs:37:51
|
37 | Some(c) => (FooCommandHandler {}).execute(c),
| ^ expected struct `FooCommand`, found &FooCommand
|
= note: expected type `FooCommand`
found type `&FooCommand`
error[E0308]: mismatched types
--> src/main.rs:41:51
|
41 | Some(c) => (BarCommandHandler {}).execute(c),
| ^ expected struct `BarCommand`, found &BarCommand
|
= note: expected type `BarCommand`
found type `&BarCommand`
How can I implement the execute()
method in a way that preserves the following requirements:
XCommand
should be totally naive of the XCommandHandler
's that execute it.CommandHandler<X>
may exist.In essence, I have a generic function fn foo<T>(v: T)
and a I wish to dispatch to a number of concrete functions fn foo1(v: Foo)
, fn foo2(v: Bar)
; how do I do that?
Is transmute
the only option?
Note that this is distinct from what Any::downcast_ref
does, which is return an &Foo
, not Foo
from the generic value v.
Upvotes: 3
Views: 95
Reputation: 35216
Just as a quick summary of the accepted answer:
Where &Any
only has:
pub fn downcast_ref<T>(&self) -> Option<&T> where T: Any
Box<Any>
implements:
pub fn downcast<T>(self) -> Result<Box<T>, Box<Any + 'static>> where T: Any
However, for complicated reasons, the documentation is on Box
not on Any
.
Upvotes: 0
Reputation: 59145
You need to go via Box
, like so:
fn execute<T>(command: T)
where
T: Any + Debug,
{
println!("Command: {:?}", command);
let any: Box<Any> = Box::new(command);
let any = match any.downcast() {
Ok(c) => return (FooCommandHandler {}).execute(*c),
Err(any) => any,
};
let any = match any.downcast() {
Ok(c) => return (BarCommandHandler {}).execute(*c),
Err(any) => any,
};
let _ = any; // avoid unused variable error
panic!("could not downcast command");
}
"But I don't wanna use a Box
!"
Just use Box
.
"But it's an allocation! I've measured the above code and proven beyond a shadow of a doubt that it's a bottleneck!"
What? Really?
"You can't prove otherwise."
Oh fine. But I do not guarantee that this will work in all cases. This is treading into "blow yourself up" territory. Do not do this unless you know you need to:
fn execute<T>(command: T)
where
T: Any + Debug,
{
use std::any::TypeId;
use std::mem;
println!("Command: {:?}", command);
macro_rules! do_cast {
($t:ty, $h:expr) => {
if TypeId::of::<T>() == TypeId::of::<$t>() {
let casted: $t = mem::transmute_copy(&command);
mem::forget(command); // we CANNOT let command drop.
$h.execute(casted);
return;
}
};
}
unsafe {
do_cast!(FooCommand, FooCommandHandler {});
do_cast!(BarCommand, BarCommandHandler {});
}
panic!("could not downcast command");
}
Upvotes: 3