英文:
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<&str, fn<E: std::error::Error>(File) -> Result<Config, E>>`
`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'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) => {
let reader = File::open(filepath).unwrap();
loader(reader)
},
None => panic!("Invalide extension"),
}
}
fn main() {
let filepath = Path::new("conf.json");
let conf = load(filepath);
println!("{:?}", 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) -> Result<Vec<std::string::String>, serde_json::Error>
found fn item fn(_) -> Result<_, serde_yaml::Error> {serde_yaml::from_reader::<_, _>}
= 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<Vec<std::string::String>, E>
because of return type
...
126 | loader(reader)
| ^^^^^^^^^^^^^^ expected Result<Vec<String>, E>
, found Result<Vec<String>, Error>
|
= note: expected enum Result<_, E>
found enum Result<_, serde_json::Error>
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<&str, fn(File) -> Config> = phf_map! {
"json" => |file| serde_json::from_reader(file).unwrap(),
"yaml" => |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<dyn Error>
.
Here's a version using Box<dyn Error>
instead of .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)?),
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论