// Make this code work
// You MAY add traits, implementations, and functions as needed
// YOu MAY add, change or remove anything that is above the "DO NOT EDIT" line.
// Your code MUST be generic to the types of arguments (it must not be specific to i64, f32, String, bool)
// Your code NEED NOT support RPC functions with more than two arguments, bonus if it does
// Bonus: If the code which depends on the number of arguments is generated by a macro
trait Tuple: Sized { }
impl<T> Tuple for T { }
fn mk_json_func<F:'static + Func<T,U>, T: 'static + Tuple, U: 'static + serde::Serialize>(func: F) -> Box<JsonFunc> {
todo!()
}
// DO NOT EDIT ANYTHING BELOW THIS LINE
type JsonFunc = dyn Func<serde_json::Value,anyhow::Result<serde_json::Value>>;
#[derive(Default)]
struct RpcHandler {
rpcs: std::collections::HashMap<String,Box<JsonFunc>>,
}
impl RpcHandler {
fn register<F: 'static + Func<T,U>, T: 'static + Tuple, U: 'static + serde::Serialize>(&mut self, name: &str, func: F) {
self.rpcs.insert(name.to_owned(), mk_json_func(func));
}
fn call(&self, name: &str, args: serde_json::Value) -> anyhow::Result<serde_json::Value> {
match self.rpcs.get(name) {
Some(val) => val.call(args),
None => anyhow::bail!("No such RPC: {name}"),
}
}
}
trait Func<T,U> {
fn call(&self, t:T) -> U;
}
impl<A,U,F> Func<(A,),U> for F where F: Fn(A)->U {
fn call(&self, t: (A,)) -> U { self(t.0) }
}
impl<A,B,U,F> Func<(A,B),U> for F where F: Fn(A,B)->U {
fn call(&self, t: (A,B)) -> U { self(t.0, t.1) }
}
// Tests
fn add_two_integers(a: i64, b: i64) -> i64 {
a+b
}
fn inverse_float(f: f32) -> f32 {
1.0/f
}
fn to_upper_case(s: String) -> String {
s.to_uppercase()
}
fn and_bit(a: bool, b: bool) -> bool {
a && b
}
fn main() -> anyhow::Result<()> {
let mut rpc = RpcHandler::default();
rpc.register("add_two_integers", add_two_integers);
rpc.register("inverse_float", inverse_float);
rpc.register("to_upper_case", to_upper_case);
rpc.register("and_bit", and_bit);
let v = rpc.call("add_two_integers", serde_json::json!([2,3]))?;
println!("2 + 3 = {v:?}");
assert_eq!(v, serde_json::json!(5));
let v = rpc.call("inverse_float", serde_json::json!([4.0]))?;
println!("1/4.0 = {v:?}");
assert_eq!(v, serde_json::json!(0.25));
let v = rpc.call("to_upper_case", serde_json::json!(["hello world!"]))?;
println!("to_upper_case(\"hello world!\") = {v:?}");
assert_eq!(v, serde_json::json!("HELLO WORLD!"));
let v = rpc.call("and_bit", serde_json::json!([true,false]))?;
println!("and_bit(true, false) = {v:?}");
assert_eq!(v, serde_json::json!(false));
Ok(())
}
It's too early to worry about optimization here. Before that, you should probably:
- Write an unoptimized version that works
- Write some
#[test]
s that will tell you if your optimizations break anything - Measure the unoptimized version to determine which parts are too slow
I don't care of speed performance, though.
Let's discuss how to enhance the implementation of dynamic function call.
Regards,
The overhead of making a dynamic function call is measured in single digit nanoseconds, so there isn't much to optimise in terms of runtime performance (this StackOverflow answer has a really good breakdown). Cloning a string takes orders of magnitude longer.
What does "enhance" mean to you?
Try to pin down exactly what you want to improve and ask yourself why you want to improve it. Once you've got a better understanding of the problem, and can articulate it, it'll be easier to find a solution.
fn register<F: 'static + Func<T,U>, T: 'static + Tuple, U: 'static + serde::Serialize>(&mut self, name: &str, func: F)
In this part, fn()->() signature turns into Func<T: Tuple, U: 'static + serde::Serialize>.
Is there other way to generalize the function signature so func can be called directly like func(10, 10)?
Regards,
Rust doesn't support variadic functions, so there's no way to write a trait bound¹ that can be called as either func(a)
and func(a,b)
. You could bound on FnMut(T)->U
, which would allow calls like func((a,))
and func((a,b))
for corresponding choices of T
.
¹ It is technically possible to write this bound, but there's no way to make any values that satisfy it.
fn mk_json_func<F: 'static + Func<T, U>, T: 'static + Tuple, U: 'static + serde::Serialize>(
func: F,
) -> Box<JsonFunc> {
let t = JsonFuncWrapper::<F, T, U> {
func, _t: PhantomData, _u: PhantomData
};
Box::new(t)
}
struct JsonFuncWrapper<F: 'static + Func<T, U>, T: 'static + Tuple, U: 'static + serde::Serialize>
{
func: F,
_t: PhantomData<T>,
_u: PhantomData<U>,
}
impl<F: 'static + Func<T, U>, T: 'static + Tuple, U: 'static + serde::Serialize>
Func<serde_json::Value, anyhow::Result<serde_json::Value>>
for JsonFuncWrapper<F, T, U>
{
fn call(&self, args: serde_json::Value) -> anyhow::Result<serde_json::Value, anyhow::Error> {
if let serde_json::Value::Array(vec) = args {
}
todo!()
}
}
The issue is how to call the original functions like 'add_two_integers' in JsonFuncWrapper::call function.
Because, the functions in the map lost their signature(arguments list, return type) and can't be called in Rust(is possible in C/C++, though).
Do you have any idea?