How do I declare a static phf::Map variable that maps string slices to functions with almost identical signatures in Rust?

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

How do I declare a static phf::Map variable that maps string slices to functions with almost identical signatures in Rust?

问题

```rust
use phf::phf_map;
use std::fs::File;
use std::path::Path;

type Config = Vec<String>;

static loaders: phf::Map<&str, fn(File) -> Result<Config, serde_json::Error>> = phf_map! {
    "json" => serde_json::from_reader,
    "yaml" => serde_yaml::from_reader,
};

fn load<P: AsRef<Path>>(filepath: P) -> Result<Config, serde_json::Error> {
    let extension = filepath.as_ref().extension().unwrap().to_str().unwrap();
    match loaders.get(extension) {
        Some(loader) => {
            let reader = File::open(filepath).unwrap();
            loader(reader)
        },
        None => panic!("Invalid extension"),
    }
}

fn main() {
    let filepath = Path::new("conf.json");
    let conf = load(filepath);
    println!("{:?}", conf)
}


```markdown
```rust
use phf::phf_map;
use std::fs::File;
use std::path::Path;

type Config = Vec<String>;

static loaders: phf::Map<&str, fn(File) -> Result<Config, serde_json::Error>> = phf_map! {
    "json" => serde_json::from_reader,
    "yaml" => serde_yaml::from_reader,
};

fn load<P: AsRef<Path>>(filepath: P) -> Result<Config, serde_json::Error> {
    let extension = filepath.as_ref().extension().unwrap().to_str().unwrap();
    match loaders.get(extension) {
        Some(loader) => {
            let reader = File::open(filepath).unwrap();
            loader(reader)
        },
        None => panic!("Invalid extension"),
    }
}

fn main() {
    let filepath = Path::new("conf.json");
    let conf = load(filepath);
    println!("{:?}", conf)
}

<details>
<summary>英文:</summary>

I have a need to declare a static `phf::Map` variable to map certain string slices to some functions (with almost identical signatures). The task revealed itself to be harder than expected ...

After battling for a while, I came out with this simplified version of the code (see below), in which I managed to get rid of the warning for the first entry in the phf::Map, but I am now stuck because the second function returns a serde_yaml::Error ...

I thought of using a generic instead of the serde_json::Error, in the function pointer, but this does not seem possible : 

`phf::Map&lt;&amp;str, fn&lt;E: std::error::Error&gt;(File) -&gt; Result&lt;Config, E&gt;&gt;`

`function pointer types may not have generic`.

At this point the only solution I see is to wrap each of the serde functions in functions with an identical signature, which would involve creating a common error type or an enum and writing two more functions ... and I&#39;d like to avoid that. Given my limited Rust experience, I thought there might just be a neat trick that I am not aware of ...

Below is a snippet that represents what I am trying to achieve and the associated compiler error.

Please note that I am not looking for alternative solutions like pattern matching and lazy evaluations.

Please also note that the code might need other fixes but I am only interested in the one I have just described.


use phf::phf_map;
use std::fs::File;
use std::path::Path;

type Config = Vec<String>;

static loaders: phf::Map<&str, fn(File) -> Result<Config, serde_json::Error>> = phf_map! {
"json" => serde_json::from_reader,
"yaml" => serde_yaml::from_reader,
};

fn load<P: AsRef<Path>, E: std::error::Error>(filepath: P) -> Result<Config, E>{

let extension = filepath.as_ref().extension().unwrap().to_str().unwrap();

match loaders.get(extension) {

    Some(loader) =&gt; {

        let reader = File::open(filepath).unwrap();
        loader(reader)

    },

    None =&gt; panic!(&quot;Invalide extension&quot;),

}

}

fn main() {
let filepath = Path::new("conf.json");

let conf = load(filepath);

println!(&quot;{:?}&quot;, conf)

}


Here is a snippet of the compiler error. The one that is the subject of the question is the first one.

error[E0308]: mismatched types
--> src/main.rs:113:15
|
113 | "yaml" => serde_yaml::from_reader,
| ^^^^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item
|
= note: expected fn pointer fn(File) -&gt; Result&lt;Vec&lt;std::string::String&gt;, serde_json::Error&gt;
found fn item fn(_) -&gt; Result&lt;_, serde_yaml::Error&gt; {serde_yaml::from_reader::&lt;_, _&gt;}
= note: when the arguments and return types match, functions can be coerced to function pointers

error[E0308]: mismatched types
--> src/main.rs:126:13
|
117 | fn load<P: AsRef<Path>, E: std::error::Error>(filepath: P) -> Result<Config, E>{
| - this type parameter ----------------- expected Result&lt;Vec&lt;std::string::String&gt;, E&gt; because of return type
...
126 | loader(reader)
| ^^^^^^^^^^^^^^ expected Result&lt;Vec&lt;String&gt;, E&gt;, found Result&lt;Vec&lt;String&gt;, Error&gt;
|
= note: expected enum Result&lt;_, E&gt;
found enum Result&lt;_, serde_json::Error&gt;

error[E0282]: type annotations needed
--> src/main.rs:140:16
|
140 | let conf = load(filepath);
| ^^^^ cannot infer type of the type parameter E declared on the function load
|
help: consider specifying the generic arguments
|
140 | let conf = load::<&Path, E>(filepath);
| ++++++++++++



</details>


# 答案1
**得分**: 2

没有额外的技巧可以在这里使用,函数的签名必须匹配。我唯一真正能提出的建议是使用闭包(如果不捕获任何环境,可以隐式转换为函数指针)并在每个函数中规范错误处理:

```rust
static LOADERS: phf::Map<&str, fn(File) -> Config> = phf_map! {
    "json" => |file| serde_json::from_reader(file).unwrap(),
    "yaml" => |file| serde_yaml::from_reader(file).unwrap(),
};

如果必须传播错误,那么它们必须被规范化为相同的类型:要么通过创建一个包含两者变体的枚举,要么映射到虚拟错误,或者是类似 Box<dyn Error> 的动态错误。


这是一个使用 Box<dyn Error> 而不是 .unwrap() 的版本:

use std::error::Error;

static loaders: phf::Map<&str, fn(File) -> Result<Config, Box<dyn Error>>> = phf_map! {
    "json" => |file| Ok(serde_json::from_reader(file)?),
    "yaml" => |file| Ok(serde_yaml::from_reader(file)?),
};
英文:

There's no additional trick you can use here, the signatures of the functions must match. The only recommendation I can really make is to use a closure (which can implicitly coerce to a function pointer if it doesn't capture any environment) and normalize the error handling within each:

static LOADERS: phf::Map&lt;&amp;str, fn(File) -&gt; Config&gt; = phf_map! {
    &quot;json&quot; =&gt; |file| serde_json::from_reader(file).unwrap(),
    &quot;yaml&quot; =&gt; |file| serde_yaml::from_reader(file).unwrap(),
};

If an error has to be propagated, then they must be normalized into the same type: either by crafting an enum with variants for both, by mapping to a dummy error, or a dynamic error like Box&lt;dyn Error&gt;.


Here's a version using Box&lt;dyn Error&gt; instead of .unwrap():

use std::error::Error;

static loaders: phf::Map&lt;&amp;str, fn(File) -&gt; Result&lt;Config, Box&lt;dyn Error&gt;&gt;&gt; = phf_map! {
    &quot;json&quot; =&gt; |file| Ok(serde_json::from_reader(file)?),
    &quot;yaml&quot; =&gt; |file| Ok(serde_yaml::from_reader(file)?),
};

huangapple
  • 本文由 发表于 2023年5月30日 04:10:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360090.html
匿名

发表评论

匿名网友

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

确定