🍣

HashMap に 関数を入れ込みたい人生だった

7 min read

まぁ、HashMapに任意の関数いれてそれを別の場所で使いたいねというお話です。

非同期じゃない関数だと

何も考えずに行けますね~と。
Boxで囲むのは dyn なので。

ソースコード

use std::{collections::HashMap};
use anyhow::Error;

fn sync_add(a: i32, b: i32) -> Result<i32, Error> {
    Ok(a + b)
}

fn sync_sub(a: i32, b: i32) -> Result<i32, Error> {
    Ok(a - b)
}

fn main() {
    let mut x:HashMap<String, Box<dyn Fn(i32, i32) -> Result<i32, Error>>> = HashMap::new();
    x.insert("add".to_string(), Box::new(sync_add));
    x.insert("sub".to_string(), Box::new(sync_sub));

    println!("{:?}", x.get("add").unwrap()(1,2));
    println!("{:?}", x.get("sub").unwrap()(1,2));

}

実行結果

Ok(3)
Ok(-1)

非同期な関数だと(ダメなケース)

よし、非同期だって、Future入れてやればいいやろと思って適当にやって出来ましたが、こういうのではエラーでした(´・ω・`)

dyn Future あたりがキモみたいですね。async関数は impl Future の型だと思いますのでその辺だがおかしな事になってるような気がします(初心者なのでよくわからない・・・・)

ソースコード

use std::{collections::HashMap, future::Future};
use anyhow::Error;


async fn async_add(a: i32, b: i32) -> Result<i32, Error> {
    Ok(a + b)
}

async fn async_sub(a: i32, b: i32) -> Result<i32, Error> {
    Ok(a - b)
}

#[tokio::main]
async fn main() {
    let mut x: HashMap<String, Box<dyn Fn(i32, i32) -> dyn Future<Output = Result<i32, Error>>>> = HashMap::new();
    x.insert("add".to_string(), Box::new(async_add));
    x.insert("sub".to_string(), Box::new(async_sub));

    println!("{:?}", x.get("add").unwrap()(1,2).await);
    println!("{:?}", x.get("sub").unwrap()(1,2).await);

}

コンパイルエラー

   Compiling playground v0.0.1 (/playground)
error[E0562]: `impl Trait` not allowed outside of function and inherent method return types
  --> src/main.rs:16:56
   |
16 |     let mut x: HashMap<String, Box<dyn Fn(i32, i32) -> impl Future<Output = Result<i32, Error>>>> = HashMap::new();
   |                                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0698]: type inside `async` block must be known in this context
  --> src/main.rs:20:44
   |
20 |     println!("{:?}", x.get("add").unwrap()(1,2).await);
   |                                            ^ cannot infer type for type `{integer}`
   |
note: the type is part of the `async` block because of this `await`
  --> src/main.rs:20:22
   |
20 |     println!("{:?}", x.get("add").unwrap()(1,2).await);
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0698]: type inside `async` block must be known in this context
  --> src/main.rs:20:46
   |
20 |     println!("{:?}", x.get("add").unwrap()(1,2).await);
   |                                              ^ cannot infer type for type `{integer}`
   |
note: the type is part of the `async` block because of this `await`
  --> src/main.rs:20:22
   |
20 |     println!("{:?}", x.get("add").unwrap()(1,2).await);
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0698]: type inside `async` block must be known in this context
  --> src/main.rs:21:44
   |
21 |     println!("{:?}", x.get("sub").unwrap()(1,2).await);
   |                                            ^ cannot infer type for type `{integer}`
   |
note: the type is part of the `async` block because of this `await`
  --> src/main.rs:21:22
   |
21 |     println!("{:?}", x.get("sub").unwrap()(1,2).await);
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0698]: type inside `async` block must be known in this context
  --> src/main.rs:21:46
   |
21 |     println!("{:?}", x.get("sub").unwrap()(1,2).await);
   |                                              ^ cannot infer type for type `{integer}`
   |
note: the type is part of the `async` block because of this `await`
  --> src/main.rs:21:22
   |
21 |     println!("{:?}", x.get("sub").unwrap()(1,2).await);
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 5 previous errors

こんな風にするとおっけーらしい

https://stackoverflow.com/questions/58704424/how-can-i-put-an-async-function-into-a-map-in-rust

検索していたらこちらのサイトが見つかりました。
そこから試行錯誤した結果です下記のように落ち着きました。

正直、クロージャーで囲んでBoxFuture返すとかそんなアイデア出ねーよ!!! とか思いました。
上のサイトではLocalBoxFutureを返す感じだった記憶がありますが、どこかで使ってたらスレッドセーフじゃなかったぽい感じのエラーが出たので、BoxFutureにしました。

use std::{collections::HashMap};
use futures::future::{BoxFuture, FutureExt};
use anyhow::Error;

type BoxedResult<T> = Result<T, Error>;
type CalcFn = Box<dyn Fn(i32, i32) -> BoxFuture<'static, BoxedResult<i32>> + Sync + Send> ;


async fn async_add(a: i32, b: i32) -> BoxedResult<i32> {
    Ok(a + b)
}

async fn async_sub(a: i32, b: i32) -> BoxedResult<i32> {
    Ok(a - b)
}


#[tokio::main]
async fn main() {
    let mut x: HashMap<&str, CalcFn> = Default::default();
    x.insert("add", Box::new(|a, b| async_add(a, b).boxed()));
    x.insert("sub", Box::new(|a, b| async_sub(a, b).boxed()));

    println!("{:?}", x.get("add").unwrap()(1,2).await);
    println!("{:?}", x.get("sub").unwrap()(1,2).await);

}

毎回HashMapにいれるのにBox::newとクロージャとかメンドイ

なんで毎回 Box::new(|a, b| ~~~ .boxed()) しなきゃいけないのか。それはそれで釈然としない。ということで専用にStructを作ったりしてみた。

例えばこんな感じであれば、 add関数側で Box::new(|a, b| ~~~ .boxed()) してくれるので使う側としてはわかりやすくて、いいのかなぁなんて。

use std::{collections::HashMap, future::Future};
use futures::future::{BoxFuture, FutureExt};
use anyhow::Error;


type BoxedResult<T> = Result<T, Error>;
type CalcFn = Box<dyn Fn(i32, i32) -> BoxFuture<'static, BoxedResult<i32>> + Sync + Send> ;


async fn async_add(a: i32, b: i32) -> BoxedResult<i32> {
    Ok(a + b)
}

async fn async_sub(a: i32, b: i32) -> BoxedResult<i32> {
    Ok(a - b)
}

struct FuncMap {
    map: HashMap<String, CalcFn>
}

impl FuncMap {
    fn new() -> Self {
        Self {
            map: HashMap::new()
        }
    }

    fn add<T>(&mut self, key: impl Into<String>, func: impl Fn(i32, i32) -> T + Send + Sync + 'static) where T: Future<Output = BoxedResult<i32>> + Send + Sync + 'static
    {
        self.map.insert(key.into(), {
            Box::new(move |a, b| {
                func(a, b).boxed()
            })
        });
    }

    fn get(&self, key: impl Into<String>) -> Option<&CalcFn> {
        self.map.get(&key.into())
    }
}




#[tokio::main]
async fn main() {
    let mut tm = FuncMap::new();
    tm.add("add", async_add);
    tm.add("sub", async_sub);

    let x = tm.get("add").unwrap()(1,2).await;
    println!("{:?}", x);
    let x = tm.get("sub").unwrap()(1,2).await;
    println!("{:?}", x);


}

最後に

非同期めんどい。よくわからない。そしてRustの型とかライフタイムとかほんと発狂しそう。
Rust全然ワカラン。。。。