rust

How to make type dynamic binding depends on variable value?


I'm creating a API function which has two parameters, I want the type of second parameter depends on the value of first one, for example:

enum Component{
   Cpu,
   Disk
}

struct CpuState {
    pub frequency: u64
}

struct DiskState {
    pub speed: u64
}

type Handler<T> = Box<dyn Fn(T) -> Box<dyn Future<Output = String> + Send> + Send + Sync>;

// the type of parameter(`<T>`) in handler is depends on the component you chosen.
fn monitor(component: Component, handler: Handler){}

The way I have tried is using trait but doesn't work:

trait StateEventHandler {
    type Component;
    type Handler;
}

impl StateEventHandler for CpuState {
    type Component = Component::Cpu; // error: expected type, found variant `Component::Cpu`
not a type
    type Handler = Handler<CpuState>;
}

impl StateEventHandler for DiskState {
    type Component = Component::DiskState; // error like above
    type Handler = Handler<DiskState>;
}

fn monitor<T: StateEventHandler>(component: T::Component, handler: T::Handler){}

How to do that?


Solution

  • Component::Cpu and Component::Disk are enum variants of Component, not individual types. And therefore, you cannot declare them as associated types on the impls of StateEventHandler.

    The broad reason why this is not allowed is, which specific enum variant was passed by the user is an information only known at runtime, but generic monomorphization happens at compile time. How do you "set" a compile time type requirement based on runtime information? You can't.

    Probably you should just declare two unit structs like so:

    struct CpuComponent;
    struct DiskComponent;
    
    impl StateEventHandler for CpuState {
        type Component = CpuComponent;
        type Handler = Handler<CpuState>;
    }
    impl StateEventHandler for DiskState {
        type Component = DiskComponent;
        type Handler = Handler<DiskState>;
    }
    

    That's how to express the semantics you need.

    If you still need an enum over CpuComponent and DiskComponent elsewhere, you can do that too. std::net::IpAddr is a good example. strum::EnumDiscriminants may also come in handy.