在Rust的HashMap中存储函数时遭遇expected fn item错误求助
在Rust中将函数存储到HashMap的解决方案
我尝试在Rust中创建一个键为字符串、值为函数的HashMap,代码如下:
use std::collections::HashMap; fn print_one(x: i32) -> String { format!("One: {}", x) } fn print_two(x: i32) -> String { format!("Two: {}", x) } fn print_three(x: i32) -> String { format!("Three: {}", x) } fn main() { let function_map: HashMap<String, fn(i32) -> String> = HashMap::from([ ("one".into(), print_one), ("two".into(), print_two), ("tree".into(), print_three) ]); println!("{}", function_map.get("one".into()).unwrap()(1)); println!("{}", function_map.get("two".into()).unwrap()(1)); println!("{}", function_map.get("tree".into()).unwrap()(1)); println!("Done"); }
编译时触发错误:
expected fn item, found a different fn item
用Vec存储类似结构可以正常运行,但HashMap会报错,不清楚如何解决。
问题原因
Rust中每个函数都是独立的具体类型(称为fn item),哪怕它们签名完全一致。当使用HashMap::from时,编译器会先推断数组第一个元素的类型——也就是print_one的具体fn item类型,而后面的print_two、print_three是不同的fn item类型,类型不匹配导致报错。
而Vec能正常工作,是因为vec![]或into()会自动寻找所有元素的共同超类型(这里就是函数指针fn(i32)->String),并自动将每个fn item转换为函数指针类型。
解决方法
有三种简单的方式可以修复这个问题:
1. 手动将每个函数转换为函数指针
给每个函数添加as fn(i32)->String强制转换,让数组所有元素类型统一为函数指针:
use std::collections::HashMap; fn print_one(x: i32) -> String { format!("One: {}", x) } fn print_two(x: i32) -> String { format!("Two: {}", x) } fn print_three(x: i32) -> String { format!("Three: {}", x) } fn main() { let function_map: HashMap<String, fn(i32) -> String> = HashMap::from([ ("one".into(), print_one as fn(i32)->String), ("two".into(), print_two as fn(i32)->String), ("tree".into(), print_three as fn(i32)->String) ]); println!("{}", function_map.get("one").unwrap()(1)); println!("{}", function_map.get("two").unwrap()(1)); println!("{}", function_map.get("tree").unwrap()(1)); println!("Done"); }
2. 显式指定数组的类型
直接告诉编译器数组的元素类型是(String, fn(i32)->String),这样编译器会自动转换每个函数:
use std::collections::HashMap; fn print_one(x: i32) -> String { format!("One: {}", x) } fn print_two(x: i32) -> String { format!("Two: {}", x) } fn print_three(x: i32) -> String { format!("Three: {}", x) } fn main() { let function_map: HashMap<String, fn(i32) -> String> = HashMap::from( [ ("one".into(), print_one), ("two".into(), print_two), ("tree".into(), print_three) ] as [(String, fn(i32)->String); 3] ); println!("{}", function_map.get("one").unwrap()(1)); println!("{}", function_map.get("two").unwrap()(1)); println!("{}", function_map.get("tree").unwrap()(1)); println!("Done"); }
3. 使用collect代替HashMap::from
利用collect会根据目标类型自动推导转换的特性,将数组转为迭代器后收集到HashMap中:
use std::collections::HashMap; fn print_one(x: i32) -> String { format!("One: {}", x) } fn print_two(x: i32) -> String { format!("Two: {}", x) } fn print_three(x: i32) -> String { format!("Three: {}", x) } fn main() { let function_map: HashMap<String, fn(i32) -> String> = [ ("one".into(), print_one), ("two".into(), print_two), ("tree".into(), print_three) ].into_iter().collect(); println!("{}", function_map.get("one").unwrap()(1)); println!("{}", function_map.get("two").unwrap()(1)); println!("{}", function_map.get("tree").unwrap()(1)); println!("Done"); }
额外说明
如果你的函数需要捕获环境(比如闭包),那函数指针就不够用了,这时候需要使用Box<dyn Fn(i32)->String>来存储,但那是另一种场景,这里你的函数都是无捕获的普通函数,用函数指针完全足够。
内容的提问来源于stack exchange,提问作者Iter Ator
相关产品推荐
相关产品推荐

