在 HashMap 中存储带有引用 arg 的未装箱闭包

2024-01-12

我正在尝试将闭包存储为 HashMap 值。如果我按值传递闭包参数,一切都会很好:

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 http://is.gd/4ySUke)

但如果我想要一个带有引用参数的闭包,事情就会向南发展:

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 http://is.gd/AKoJJv)

我读了答案如何将代码重写为新的未装箱闭包 https://stackoverflow.com/questions/27815315/how-to-rewrite-code-to-new-unboxed-closures,并尝试将地图建筑分解为自己的功能,以便有一个地方可以悬挂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 http://is.gd/Lau7C9)

这样做的正确方法是什么?


我的解决方案:

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

原料:

  1. 返回一个特征对象
  2. 为编译器提供有关闭包参数类型的一些帮助:Box::new(|&mut: s: &str|

老实说,我并不是 100% 确定 #2 的原因(我的意思是,至少将其省略应该会给出更容易理解的错误消息)。可能是 rustc 的问题。

在#1上,我几乎确定它是必需的,因为您无法为从函数返回的闭包命名具体的返回类型(它是由编译器动态创建的匿名类型),因此 Trait 对象现在应该是返回闭包的唯一方法。

Appendix回复评论:

想象你有一个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>();

即,如所写的,该函数不返回特征对象。它承诺返回一个具有调用者选择的任何具体类型的 Box。这就是为什么实际实现该功能并不容易。它应该知道如何从无到有地创建多种类型。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 HashMap 中存储带有引用 arg 的未装箱闭包 的相关文章

随机推荐