📑

Rustで関数を値のように扱う時は型に注意

2024/03/10に公開


Rustを書いててハマった備忘1です。[rustc 1.76.0]
知識無い上に構造体とか色々使ってたところで出たので特定になかなか時間がかかりました。

起こった問題

コード

use std::collections::HashMap;

fn main() {
    let mut hm1: HashMap<&str, fn()> = HashMap::new();
    hm1.insert("foo", foo);
    hm1.insert("bar", bar);

    hm1.get("foo").unwrap()();
    hm1.get("bar").unwrap()();

    let hm2: HashMap<&str, fn()> = HashMap::from([("foo", foo),("bar", bar)]);

    hm2.get("foo").unwrap()();
    hm2.get("bar").unwrap()();
}

fn foo() { println!("{}", "foo"); }
fn bar() { println!("{}", "bar"); }

結果

vscode ➜ /workspaces/z_rust_sandbox (master) $ cargo run
   Compiling z_rust_sandbox v0.1.0 (/workspaces/z_rust_sandbox)
error[E0308]: mismatched types
  --> src/main.rs:11:72
   |
11 |     let hm2: HashMap<&str, fn()> = HashMap::from([("foo", foo),("bar", bar)]);
   |                                                                        ^^^ expected fn item, found a different fn item
   |
   = note: expected fn item `fn() {foo}`
              found fn item `fn() {bar}`
   = note: different fn items have unique types, even if their signatures are the same
   = help: consider casting both fn items to fn pointers using `as fn()`

error[E0308]: mismatched types
  --> src/main.rs:11:36
   |
11 |     let hm2: HashMap<&str, fn()> = HashMap::from([("foo", foo),("bar", bar)]);
   |              -------------------   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `HashMap<&str, fn()>`, found `HashMap<&str, fn() {foo}>`
   |              |
   |              expected due to this
   |
   = note: expected struct `HashMap<&_, fn()>`
              found struct `HashMap<&_, fn() {foo}>`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `z_rust_sandbox` (bin "z_rust_sandbox") due to 2 previous errors

わかったこと

  • 全ての関数は定義毎に固有の型を持つ
    • 上記コードではfoo型とbar型がある
    • シグネチャが完全に同じでも同じ型ではない
      • シグネチャ: 引数や戻り値、それらの型等
  • 関数名をそのまま値のように使うと関数定義型(定義毎の固有の型)として解釈される
    • 関数定義型は0バイト
  • 関数を値のように使いたい時(関数ポインタが必要な時)は関数ポインタ型で扱う
    • fnというキーワードが関数ポインタ型を表す
  • ジェネリックな型に関数を指定する時などは関数ポインタ型として明示する必要がある

問題の解決

use std::collections::HashMap;
type FnPtr = fn();

fn main() {
    let mut hm1: HashMap<&str, FnPtr> = HashMap::new();
    hm1.insert("foo", foo);
    hm1.insert("bar", bar);

    hm1.get("foo").unwrap()();
    hm1.get("bar").unwrap()();

    let hm2: HashMap<&str, FnPtr> = HashMap::from([("foo", foo as FnPtr),("bar", bar as FnPtr)]);

    hm2.get("foo").unwrap()();
    hm2.get("bar").unwrap()();
}

fn foo() { println!("{}", "foo"); }
fn bar() { println!("{}", "bar"); }

愚痴

insertがエラーにならないのはうまくやってる&楽だからなんだろうけど、初心者からすると混乱する。

参考文献

Discussion