Handlers have different bodies but must share one signature, so each is a trait object:
type Handler = Box<dyn Fn(&[i64]) -> Result<i64, RpcError>>;
struct Router { routes: HashMap<&'static str, Handler> }
The box is not ceremony — it is what lets closures of different concrete types live in one collection. The cost is one pointer indirection per call, against a hash lookup that is already dominated by the socket read.
A hand-written match on the method name compiles to the same dispatch. What it cannot do is be extended at runtime: no module registering its own methods at startup, no rpc.discover, no per-method metrics enumerated from the table, and every new method recompiles the file that owns the match.
One deterministic-output trap: HashMap iteration order is unspecified and varies per process. Any method listing must be sorted before it is printed or hashed.
