Хранение распакованного закрытия со ссылочным аргументом в HashMap

Я пытаюсь сохранить замыкание как значение 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()),
        _ => ()
    }
}

( детский манеж)

Но если я хочу замыкание, которое принимает ссылочный аргумент, дела пойдут на юг:

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

( детский манеж)

Я прочитал ответ " Как переписать код в новые распакованные замыкания" и попытался разбить здание карты на его собственную функцию, чтобы можно было повесить 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); })));
                                                                  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

( детский манеж)

Какой правильный способ сделать это?

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(|&mut: s: &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>();

т.е. функция, как написано, не возвращает объект черты. Обещает вернуть коробку с любым конкретным типом, который выберет вызывающий абонент. Вот почему не так просто реализовать эту функцию. Нужно уметь создавать несколько типов из ничего.

Другие вопросы по тегам