🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

Q&A

解決済

2回答

4393閲覧

Rust match式でクロージャを返したい

namnium1125

総合スコア2045

Rust

Rustは、MoFoが支援するプログラミング言語。高速性を維持しつつも、メモリ管理を安全に行うことが可能な言語です。同じコンパイル言語であるC言語やC++では困難だったマルチスレッドを実装しやすく、並行性という点においても優れています。

1グッド

1クリップ

投稿2019/10/16 13:50

編集2019/10/16 13:53

Rustを始めてまだ日が浅いものです。

イテレータのmapメソッドに渡す関数を状態によって変化させたいと考え、match式でクロージャを返すようなコードを書いてみたのですが、match arms have incompatible typesというエラーを返されました。型の不一致的な問題だとは思うのですが、こういったことを実現したい場合どのようにすればよいのでしょうか?

Rust

1use std::io; 2 3fn main() { 4 let mut input = String::new(); 5 io::stdin().read_line(&mut input).unwrap(); 6 let num: u32 = input.trim().parse().unwrap(); 7 8 let func = match num { 9 0 => |i| i * i, 10 1 => |i| i + 1, 11 2 => |i| i + i, 12 _ => |_| 0, 13 }; 14 15 println!("func({}) = {}", 5, func(5)); 16}
$ cargo run Compiling playground v0.1.0 (省略) error[E0308]: match arms have incompatible types --> src/main.rs:10:14 | 8 | let func = match num { | ________________- 9 | | 0 => |i| i * i, | | --------- this is found to be of type `[closure@src/main.rs:9:14: 9:23] ` 10 | | 1 => |i| i + 1, | | ^^^^^^^^^ expected closure, found a different closure 11 | | 2 => |i| i + i, 12 | | _ => |_| 0, 13 | | }; | |_____- `match` arms have incompatible types | = note: expected type `[closure@src/main.rs:9:14: 9:23]` found type `[closure@src/main.rs:10:14: 10:23]` = note: no two closures, even if identical, have the same type = help: consider boxing your closure and/or using it as a trait object error: aborting due to previous error For more information about this error, try `rustc --explain E0308`. error: Could not compile `playground`. To learn more, run the command again with --verbose.

色々検索ワードを考えてはみたのですが一向に見つからなかったので質問した次第です。もとい、当方Rustの型とクロージャへの理解がかなり不足しているのだと思います。何か参考になるページ等ありましたら紹介してただければと思います。

回答、よろしくお願いいたします

バージョン

$ rustc --version rustc 1.37.0 (eae3437df 2019-08-13)

他に必要な情報があれば修正欄にお願いします。

Paalon👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

console

1 = note: expected type `[closure@src/main.rs:9:14: 9:23]` 2 found type `[closure@src/main.rs:10:14: 10:23]` 3 = note: no two closures, even if identical, have the same type

エラーメッセージにも書かれていますが、Rustでは一つ一つのクロージャがそれぞれ別の型になります。一方でmatch式は全ての腕(matchの節)が同じ型の値を返すことを要求します。このような不一致があるためエラーになっています。

対処法は2つあります。1つ目の方法はエラーと一緒に表示されているhelpに従って、クロージャをトレイトオブジェクトに変換することです。

console

1 = help: consider boxing your closure and/or using it as a trait object

以下のように変数funcの型としてBox<dyn Fn(i32) -> i32>を指定し、個々のクロージャをBox::new()で囲みます。

rust

1use std::io; 2 3fn main() -> Result<(), Box<dyn std::error::Error>> { 4 let mut input = String::new(); 5 io::stdin().read_line(&mut input)?; 6 let num: u32 = input.trim().parse()?; 7 8 // Box化することでトレイトオブジェクトへ型強制する 9 let func: Box<dyn Fn(i32) -> i32> = match num { 10 0 => Box::new(|i| i * i), 11 1 => Box::new(|i| i + 1), 12 2 => Box::new(|i| i + i), 13 _ => Box::new(|_| 0), 14 }; 15 16 println!("func({}) = {}", 5, func(5)); 17 Ok(()) 18}

変数の型のところのdyn Fn(i32) -> i32の部分が、型の名前ではなくて、dyn + トレイト名になっているのがポイントです。

2つ目の方法は、使える場面が限られますが、クロージャではなくて無名関数に対する関数ポインタにすることです。以下のように、変数funcの型をfn(i32) -> i32にします。(fnfは小文字)

rust

1use std::io; 2 3fn main() -> Result<(), Box<dyn std::error::Error>> { 4 let mut input = String::new(); 5 io::stdin().read_line(&mut input)?; 6 let num: u32 = input.trim().parse()?; 7 8 // このケースではクロージャでなくて無名関数でもよい。関数ポインタに型強制する 9 let func: fn(i32) -> i32 = match num { 10 0 => |i| i * i, 11 1 => |i| i + 1, 12 2 => |i| i + i, 13 _ => |_| 0, 14 }; 15 16 println!("func({}) = {}", 5, func(5)); 17 Ok(()) 18}

この方法が使えるのは、クロージャがその環境に自由変数(自分の外側にある変数)を捕捉していないときだけです。

2つの方法の違いは以下のとおりです。

1つ目の方法

  • クロージャの呼び出しが動的ディスパッチになるので、ポインタを2回たどることになり、性能面でわずかに不利

2つ目の方法

  • 無名関数の呼び出しはポインタを1回たどるだけなので、性能面でわずかに有利
  • 使える場面が限定される。自由変数を捕捉してないときだけ使える

追記

参考資料についてですが、オンラインではあまり丁寧な説明が見つからないので、一応、共著で出版した書籍を紹介させてください。もし大きな書店に行かれることがあったら、立ち読みでかまいませんので読んでみてください。関連するページは以下のとおりです。

実践Rust入門(技術評論社、2019年5月発行)

  • 3-5-4項(P123) クロージャの型について
  • 4章コラム(P159) 関数ポインタとクロージャ
  • 7-12節(P299) クロージャと所有権
  • 8-3-2項(P323) トレイトオブジェクトと動的ディスパッチのしくみ

投稿2019/10/16 15:23

編集2019/10/16 15:45
tatsuya6502

総合スコア2046

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

namnium1125

2019/10/16 15:34

回答ありがとうございます、なるほど!今回挙げたコードの場合は自由変数がないのでクロージャではなく無名関数として扱えるのですね、回答ありがとうございます。BAとさせていただきますm(_ _)m
namnium1125

2019/10/16 15:45

Rust公式ガイドの方( https://doc.rust-jp.rs/book/second-edition/ )で勉強していたのですが、その他にわかりやすい書籍がないか探していたところです。読んでみたいと思います、ありがとうございますm(_ _)m
guest

0

間接参照のBoxを使うという手があるようです。しかしこの方法であっているのだろうか...(コンパイルは通り希望通りの動作もしました)

Rust

1use std::io; 2 3fn main() { 4 let mut input = String::new(); 5 io::stdin().read_line(&mut input).unwrap(); 6 let num: u32 = input.trim().parse().unwrap(); 7 8 let func: Box<dyn Fn(i32) -> i32> = match num { 9 0 => Box::new(|i| i * i), 10 1 => Box::new(|i| i + 1), 11 2 => Box::new(|i| i + i), 12 _ => Box::new(|_| 0), 13 }; 14 15 println!("func({}) = {}", 5, func(5)); 16}

投稿2019/10/16 15:00

編集2019/10/16 15:01
namnium1125

総合スコア2045

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問