在 HashMap 中存储带有引用 arg 的未装箱闭包
Storing an unboxed closure with a reference arg in a HashMap
我正在尝试将闭包存储为 HashMap 值。如果我按值传递闭包 arg,一切正常:
use std::collections::hash_map::HashMap;
fn main() {
let mut cmds: HashMap<String, Box<FnMut(String)->()>>
= HashMap::new();
cmds.insert("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); }));
match cmds.get_mut("ping") {
Some(f) => f("pong".to_string()),
_ => ()
}
}
(playpen)
但是如果我想要一个带有引用 arg 的闭包,事情就会变得糟糕:
use std::collections::hash_map::HashMap;
fn main() {
let mut cmds: HashMap<String, Box<FnMut(&str)->()>>
= HashMap::new();
cmds.insert("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); }));
match cmds.get_mut("ping") {
Some(f) => f("pong"),
_ => ()
}
}
<anon>:8:37: 8:78 error: type mismatch: the type `closure[<anon>:8:46: 8:77]` implements the trait `core::ops::FnMut(_)`, but the trait `for<'r> core::ops::FnMut(&'r str)` is required (expected concrete lifetime, found bound lifetime parameter )
<anon>:8 cmds.insert("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); }));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:8:37: 8:78 note: required for the cast to the object type `for<'r> core::ops::FnMut(&'r str)`
<anon>:8 cmds.insert("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); }));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
(playpen)
我阅读了 的答案,并尝试将 map 构建到它自己的函数中,以便有地方挂起 where
子句,但没有骰子:
use std::collections::hash_map::HashMap;
fn mk_map<F>() -> HashMap<String, (String, Box<F>)>
where F: for<'a> FnMut(&'a str) -> ()
{
let mut cmds: HashMap<String, (String, Box<F>)> = HashMap::new();
cmds.insert("ping".to_string(), ("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); })));
cmds
}
fn main() {
let cmds = mk_map();
match cmds.get_mut("ping") {
Some(&mut (_, ref mut f)) => f("pong"),
_ => println!("invalid command")
}
}
<anon>:8:58: 8:99 error: mismatched types: expected `Box<F>`, found `Box<closure[<anon>:8:67: 8:98]>` (expected type parameter, found closure)
<anon>:8 cmds.insert("ping".to_string(), ("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); })));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(playpen)
正确的做法是什么?
我的解决方案:
#![allow(unstable)]
use std::collections::hash_map::HashMap;
// #1 returning a trait object
fn mk_map<'a>() -> HashMap<String, (String, Box<FnMut(&str) + 'a>)> {
let mut cmds : HashMap<_, (_, Box<FnMut(&str)>)> = HashMap::new();
cmds.insert("ping".to_string(), ("ping".to_string(),
Box::new(|&mut: s: &str| { println!("{}", s); })));
// #2 ^-- give a little help to the compiler here
cmds
}
fn main() {
let mut cmds = mk_map();
// minor change: cmds needs to be mutable
match cmds.get_mut("ping") {
Some(&mut (_, ref mut f)) => f("pong"),
_ => println!("invalid command")
}
}
成分:
- return 特征对象
- 在闭包参数的类型上给编译器一些帮助:
Box::new(|&mut: s: &str|
老实说,我不是 100% 确定 #2 的原因(我的意思是,至少将其排除在外应该会给出更易于理解的错误消息)。可能是 rustc 的问题。
在 #1 上,我几乎可以肯定它是必需的,因为您不能为从函数 return 中创建的闭包命名具体的 return 类型(它是在由编译器飞行),所以 Trait 对象现在应该是 return 闭包的唯一途径。
附录
回复评论:
假设您有一个由几种类型实现的 trait Foo {}
:
trait Foo {}
impl Foo for u32 {}
impl Foo for Vec<f32> {}
如果您像 mk_map 那样编写一个函数(我们称它为 make_foo),我评论说它很难实现。让我们看看:
fn mk_foo<F>() -> Box<F> where F: Foo {
unimplemented!()
}
mk_foo 的签名说我应该能够调用任何实现 Foo 的类型的函数。所以这应该都是有效的:
let a: Box<Vec<f32>> = mk_foo::<Vec<f32>>();
let b: Box<u32> = mk_foo::<u32>();
即如所写,该函数不是 returning 特征对象。它承诺 return 具有调用者选择的任何具体类型的 Box。这就是为什么实际实现该功能并不容易。它应该知道如何从无到有地创建多种类型。
我正在尝试将闭包存储为 HashMap 值。如果我按值传递闭包 arg,一切正常:
use std::collections::hash_map::HashMap;
fn main() {
let mut cmds: HashMap<String, Box<FnMut(String)->()>>
= HashMap::new();
cmds.insert("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); }));
match cmds.get_mut("ping") {
Some(f) => f("pong".to_string()),
_ => ()
}
}
(playpen)
但是如果我想要一个带有引用 arg 的闭包,事情就会变得糟糕:
use std::collections::hash_map::HashMap;
fn main() {
let mut cmds: HashMap<String, Box<FnMut(&str)->()>>
= HashMap::new();
cmds.insert("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); }));
match cmds.get_mut("ping") {
Some(f) => f("pong"),
_ => ()
}
}
<anon>:8:37: 8:78 error: type mismatch: the type `closure[<anon>:8:46: 8:77]` implements the trait `core::ops::FnMut(_)`, but the trait `for<'r> core::ops::FnMut(&'r str)` is required (expected concrete lifetime, found bound lifetime parameter )
<anon>:8 cmds.insert("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); }));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:8:37: 8:78 note: required for the cast to the object type `for<'r> core::ops::FnMut(&'r str)`
<anon>:8 cmds.insert("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); }));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
error: aborting due to previous error
(playpen)
我阅读了 where
子句,但没有骰子:
use std::collections::hash_map::HashMap;
fn mk_map<F>() -> HashMap<String, (String, Box<F>)>
where F: for<'a> FnMut(&'a str) -> ()
{
let mut cmds: HashMap<String, (String, Box<F>)> = HashMap::new();
cmds.insert("ping".to_string(), ("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); })));
cmds
}
fn main() {
let cmds = mk_map();
match cmds.get_mut("ping") {
Some(&mut (_, ref mut f)) => f("pong"),
_ => println!("invalid command")
}
}
<anon>:8:58: 8:99 error: mismatched types: expected `Box<F>`, found `Box<closure[<anon>:8:67: 8:98]>` (expected type parameter, found closure)
<anon>:8 cmds.insert("ping".to_string(), ("ping".to_string(), Box::new(|&mut:s| { println!("{}", s); })));
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(playpen)
正确的做法是什么?
我的解决方案:
#![allow(unstable)]
use std::collections::hash_map::HashMap;
// #1 returning a trait object
fn mk_map<'a>() -> HashMap<String, (String, Box<FnMut(&str) + 'a>)> {
let mut cmds : HashMap<_, (_, Box<FnMut(&str)>)> = HashMap::new();
cmds.insert("ping".to_string(), ("ping".to_string(),
Box::new(|&mut: s: &str| { println!("{}", s); })));
// #2 ^-- give a little help to the compiler here
cmds
}
fn main() {
let mut cmds = mk_map();
// minor change: cmds needs to be mutable
match cmds.get_mut("ping") {
Some(&mut (_, ref mut f)) => f("pong"),
_ => println!("invalid command")
}
}
成分:
- return 特征对象
- 在闭包参数的类型上给编译器一些帮助:
Box::new(|&mut: s: &str|
老实说,我不是 100% 确定 #2 的原因(我的意思是,至少将其排除在外应该会给出更易于理解的错误消息)。可能是 rustc 的问题。
在 #1 上,我几乎可以肯定它是必需的,因为您不能为从函数 return 中创建的闭包命名具体的 return 类型(它是在由编译器飞行),所以 Trait 对象现在应该是 return 闭包的唯一途径。
附录 回复评论:
假设您有一个由几种类型实现的 trait Foo {}
:
trait Foo {}
impl Foo for u32 {}
impl Foo for Vec<f32> {}
如果您像 mk_map 那样编写一个函数(我们称它为 make_foo),我评论说它很难实现。让我们看看:
fn mk_foo<F>() -> Box<F> where F: Foo {
unimplemented!()
}
mk_foo 的签名说我应该能够调用任何实现 Foo 的类型的函数。所以这应该都是有效的:
let a: Box<Vec<f32>> = mk_foo::<Vec<f32>>();
let b: Box<u32> = mk_foo::<u32>();
即如所写,该函数不是 returning 特征对象。它承诺 return 具有调用者选择的任何具体类型的 Box。这就是为什么实际实现该功能并不容易。它应该知道如何从无到有地创建多种类型。