I'm implementing an RPC system (the following code is simplified for illustration, it cannot be compiled, for a fully runnable example, please see this playground link):
trait RpcHandler<Req> {
fn handle(&self, req: Req);
}
struct CommonRequest;
struct MyStruct;
impl RpcHandler<CommonRequest> for MyStruct {
fn handle(&self, req: CommonRequest) {
let rpc_method_name = get_rpc_method_name_from_rpc_context();
match rpc_method_name {
"foo" => self.foo(req),
"bar" => self.bar(req),
_ => (),
}
}
}
type HandlerMap = HashMap<String, Box<dyn RpcHandler>>;
static HANDLERS: HandlerMap;
fn register_rpc<Req>(method_name: &str, handler: &impl RpcHandler<Req>) {
HANDLERS.insert(method_name, Box::new(handler));
}
fn on_rpc_call(buf: &[u8]) {
let rpc_method_name: &str = decode_method_name_from_bytes(buf);
let req = decode_request_struct_from_bytes(buf);
let handler = HANDLERS.get(rpc_method_name).unwrap();
handler.handle(req);
}
fn main() {
let s = MyStruct;
register_rpc("foo", &s);
register_rpc("bar", &s);
let buf: Vec<u8> = recv_some_bytes_from_network();
on_rpc_call(&buf);
}
As you can see, I have actually two RPC methods foo and bar for MyStruct, both methods are registered as dynamic handlers. When a request is received, I decode the method name and request body from that, and find the proper handler by name, and then call that handler with decoded request body.
However, foo and bar share a same request type CommonRequest, so I can ONLY implement RpcHandler<CommonRequest> for MyStruct ONCE, so I have to write the code of foo and bar in the same implementation of RpcHandler<CommonRequest>. I think the more idiomatic way should be adding RPC method name to the trait, so I can implement RpcHandler for each RPC method (and request type) on MyStruct:
trait RpcHandler<const RpcMethodName: &'static str, Req> {
fn handle(&self, req: Req);
}
impl RpcHandler<"foo", CommonRequest> for MyStruct {
fn handle(&self, req: CommonRequest) {
self.foo(req);
}
}
impl RpcHandler<"bar", CommonRequest> for MyStruct {
fn handle(&self, req: CommonRequest) {
self.bar(req);
}
}
However, the compiler complains that &'static str is forbidden as the type of a const generic parameter, any idiomatic/elegant workaround of that? Thanks.
The compiler forbids
&stras aconstgeneric, but I don't think it would actually help in your situation. Even if you usedi32, which is allowed, you would need to make the actual call like so:But this is a
constgeneric, so you cannot e.g. pass a variable:So you have to still implement the actual dispatching logic, because (as expected) the type/const system cannot infer what values will flow into the program at runtime.
Looking at your code, though, there are some things you can put into the traits rather than have to repeat throughout the code. Namely, you can associate RPC handler implementations with their name using an associated constant:
Each implementation must provide a value for
NAME. Then, you can use the constant e.g. in the register method:After some discussion in the comments: marker traits/types can be used to achieve something similar to what the
constgenerics in the OP were meant to do. Concretely, we can define a marker trait, which does nothing except identify some types as being "methods":Then, some empty types will implement this trait:
With this setup, type parameters can be constrained to
MethodNameto solve the problem of multiple implementations of the same trait for the same struct, but for different methods:As a bonus, the
MethodNametrait can also contain aconst NAME: &'static str;, if the implementation ever needs to reference the method name as a string.