如何在具有通用参数的函数上实现一个特质

huangapple go评论54阅读模式
英文:

How to implement a trait on a function with generic argument

问题

我正在尝试实现以下代码:

#[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);
}

我尝试了几种方法来允许注册一个函数,然后可以以正确的参数稍后调用它。最有前途的方法是创建一个指定了call_with_json()方法的trait,然后为类型fn(T) -> i32实现它。

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)
    }
}

这是完整实现的 playground 链接:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=566e33aab2e3c6d3a090d3b9831a4358

Rust 一直告诉我,对于 fn 项 fn(SomeFoo) -&gt; i32 {test},未实现 trait CallHandler。不确定我漏掉了什么。

英文:

I am trying to achieve the following code:

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

fn test(req: SomeFoo) -&gt; i32 {
    println!(&quot;Value: {:?}&quot;, req);
    5
}

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

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

    let result = handlers.dispatch(&quot;foobar1&quot;, payload);
    println!(&quot;Result: {}&quot;, 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(&amp;self, req: &amp;str) -&gt; i32;
}

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

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

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

Not sure what I am missing here.

答案1

得分: 1

在Rust中,每个函数都有其自己的“函数项类型”,通常在需要时会被隐式转换为函数指针。函数项类型是零大小的类型;因为它唯一标识单个函数,所以不需要存储任何运行时信息。将其转换为函数指针会丢失有关特定函数的信息,而优先考虑运行时指针。

问题出在你的代码上,Rust不会应用隐式转换来使类型满足trait约束,因为可能有多种不同的转换方式来实现这一点。因此,你需要将转换为函数指针类型的操作明确化以使代码编译:

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

(请注意,你还需要删除HandlerMap::add()的未使用的泛型参数R。由于它完全没有被使用,编译器将无法推断它。)

一般来说,最好对满足trait约束Fn(T) -> i32的任何类型进行全局实现。然而,在这种情况下,由于泛型参数T,这是不可能的,因为在一般情况下无法从实现Fn(T) -> i32的类型中推断出它。因为类型可能为多个类型T实现Fn(T) -> i32(当然,在实践中这可能非常不寻常,但对于编译器来说,重要的是它是可能的。)

英文:

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(&quot;foobar1&quot;, &amp;(test as fn(SomeFoo) -&gt; 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) -&gt; 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) -&gt; i32 in the general case, since a type might implement Fn(T) -&gt; 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.)

答案2

得分: 0

我实际上成功找到了一种方法,可以在不使用 as F(_) 的情况下实现,尽管它需要在堆上创建一个闭包。

英文:

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&amp;mode=debug&amp;edition=2021&amp;gist=ef64cc2bc094ac4c4d3ba2b45c9d1c23

huangapple
  • 本文由 发表于 2023年4月19日 18:19:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/76053349.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定