Doug
Doug

Reputation: 35216

How do you convert an instance of generic T into a concrete instance in Rust?

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:

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

Answers (2)

Doug
Doug

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

DK.
DK.

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

Related Questions