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

3

我正在尝试将闭包作为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)

但如果我想要一个带有引用参数的闭包,情况就会变得棘手:

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

如何正确地完成这个任务?

1个回答

3

我的解决方案:

#![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. 返回一个trait对象
  2. 为闭包的参数类型提供一些帮助:Box :: new(|&mut:s:&str |

说实话,我不确定#2的原因(我的意思是,至少省略它应该会给出更易懂的错误消息)。可能是rustc的问题。

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

附录回复评论:

想象一下你有一个由几种类型实现的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>();

换句话说,这个函数并没有返回一个特质对象。它承诺返回一个调用者选择的任何具体类型的盒子。这就是为什么实际上很难实现这个函数。它应该知道如何从无中创建多种类型。


1
闭包参数上的类型提示似乎对第一次尝试的代码起到了作用:http://is.gd/RU3bL1 - jfager
@jfager 在第一次尝试中,您正在做同样的事情:将 trait 对象插入到哈希映射中。 - Paolo Falabella
我不确定我理解你的意思。我一直在尝试插入一个特质对象,这就是 Box 的作用。像你说的那样,闭包参数需要类型提示并不明显。 - jfager
@jfager 在你使用 mk_map 的尝试中,你的签名表明对于任何实现了你 where 子句中 trait 的具体类型 F,该函数将能够返回一个包含该具体类型的 Box。这与 trait 对象不同,是一个非常难以满足的签名。 - Paolo Falabella
啊,好的,我其实并不关心mk_map,那只是试图解决实际问题的半心半意的尝试。虽然我不完全理解为什么这是一个难以填写的签名,因为它是一个具体类型,但它不是一个命名的具体类型,这就是我理解的你不能使用闭包的原因,因为类型是一次性的。谢谢。 - jfager
@jfager 我在我的答案中直接回应了你的评论,因为在评论中很难做到。 - Paolo Falabella

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接