-1

I am trying to achieve the following code:

#[derive(Debug, Serialize, Deserialize)]
struct SomeFoo {
    name: String,
    age: i32,
}

fn test(req: SomeFoo) -> i32 {
    println!("Value: {:?}", req);
    5
}

fn main() {
    let mut handlers = HandlerMap::new();
    handlers.add("foobar1", &test);

    let payload = r#"
        {
            "name": "John Doe",
            "age": 43
        }"#;

    let result = handlers.dispatch("foobar1", payload);
    println!("Result: {}", result);
}

I tried a few approaches to allow to register a function that can than be later called with the correct argument. The most promising was to create a trait that specified a method call_with_json() and then implement it for the type fn(T).

trait CallHandler {
    fn call_with_json(&self, req: &str) -> i32;
}

impl<T> CallHandler for fn(T) -> i32
where
    T: DeserializeOwned,
{
    fn call_with_json(&self, req: &str) -> i32 {
        let req: T = serde_json::from_str(req).expect("bad json");
        (self)(req)
    }
}

Here playground link with the full impl. https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=566e33aab2e3c6d3a090d3b9831a4358

Rust keeps telling me that the trait CallHandler is not implemented for fn item fn(SomeFoo) -> i32 {test}

Not sure what I am missing here.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
butterbrot
  • 980
  • 6
  • 10

2 Answers2

1

In Rust, each function has its own "function item type", which will usually be implicitly coerced into a function pointer when needed. The function item type is a zero-sized type; since it uniquely identifies a single function, it doesn't need to store any runtime information. Converting it to a function pointer drops the information about the specific function from the compile-time type in favour of a runtime pointer.

The problem with your code is that Rust won't apply implicit coercions to make a type satisfy a trait bound, since there may be various different coercions to do this. So you need to make the cast to the function pointer type explicit to make the code compile:

handlers.add("foobar1", &(test as fn(SomeFoo) -> i32));

(Note that you also need to remove the unused generic parameter R for HandlerMap::add(). Since it's completely unused, the compiler will understandably be unable to infer it.)

It would be generally preferable to have a blanket implementation for any type satisfying the trait bound Fn(T) -> i32. However, that's not possible in this case due to the generic parameter T, which is impossible to infer from the type implementing Fn(T) -> i32 in the general case, since a type might implement Fn(T) -> i32 for multiple types T. (Of course that would be very unusual in practice, but all that matters for the compiler is that it's possible.)

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Came to the same solution, and prepared a working playground here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6c4d3db8d98b8a90579dc400f9e0588e – CoronA Apr 19 '23 at 10:51
  • Thank you @sven-marnach - a bit disappointed that this does not work the as I expected but the function item was the hint I need to understand why :) – butterbrot Apr 19 '23 at 13:08
0

I actually managed to find a way to this without the as F(_) bit, though it requires a closure on the heap.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=ef64cc2bc094ac4c4d3ba2b45c9d1c23

butterbrot
  • 980
  • 6
  • 10