【问题标题】:Storing an unboxed closure with a reference arg in a HashMap在 HashMap 中使用引用 arg 存储未装箱的闭包
【发布时间】:2015-01-12 16:50:41
【问题描述】:

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

我阅读了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)

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

【问题讨论】:

    标签: closures rust


    【解决方案1】:

    我的解决方案:

    #![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(|&amp;mut: s: &amp;str|

    老实说,我不是 100% 确定 #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>();
    

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

    【讨论】:

    • 只是闭包 arg 上的类型提示似乎对第一次尝试代码起作用:is.gd/RU3bL1
    • @jfager 在第一次尝试中你正在做同样的事情:在哈希图中插入一个特征对象。
    • 不确定我是否理解您的意思。我一直在尝试插入一个特征对象,这就是 Box 的用途。正如你所说,闭包 arg 需要类型提示这一事实并不明显。
    • @jfager 在您尝试使用 mk_map 时,您的签名表明对于在 where 子句中实现该特征的任何具体类型 F,该函数将能够返回具有该具体类型的 Box。这不同于 trait 对象和非常难以实现的签名。
    • 啊,好吧,我实际上并不关心 mk_map,这是解决实际问题的半心半意的尝试。不过,我不完全理解为什么它是一个难以填充的签名 - 它是一个具体类型,但它不是一个 named 具体类型,这是我理解的你不能做的事情' t do w/closures b/c 这些类型是一次性的。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多