Rustの勉強(その6)
前回の続きです。ところでタブ固定してたんで気付かなかったんですが、この食事する哲学者の問題のチュートリアル、公式のドキュメントにリンクが見当たらないようになってるんですけど、どういうことでしょうね?
mao-instantlife.hatenablog.com
expression based
なんと訳せばいいのかよく分からないんですが、全ては式として定義されて値がリターンされますよ、で、指定がなければ最後の式がリターンされますよ、というScalaとかでもおなじみの挙動のことのようです。
インスタンスの生成(newを使わない場合)
let px = Philosopher { name = "takaaki yoshimoto".to_string() };
でもいけるっぽいですね。new使う方がすっきりしてるので(今回は)そっちでいきます。
配列の定義
let philosopers = vec![ Philosopher::new("Judith Butler"), Philosopher::new("Gilles Deleuze"), Philosopher::new("Karl Marx"), Philosopher::new("Emma Goldman"), Philosopher::new("Michel Foucault"), ];
動的配列 Vec<T>' を作っています。特にチュートリアルに記載がないですが、今までの記法からして
vec!` は動的配列を生成するためのマクロなんでしょうかね。
配列のイテレーションには for
を使います。
for p in philosophers { p.eat(); }
よく見る記法です。
インスタンスメソッド(method)の追加
Philosopher
型にメソッドを追加します。
fn eat(&self) { println!("{} is done eating.", self); }
associated functionの定義 -> selfパラメータなし methodの定義 -> 明示的にselfパラメータをつける
並列処理
哲学者どもに並列で食事をしてもらいます。
let handles: Vec<_> = philosophers.into_iter().map(|p| { thread::spawn(move || { p.eat(); }) }).collect(); for h in handles { h.join().unwrap(); }
最初の map
の実行で配列 philosophers
の各要素に対して eat
メソッドを実行するスレッドのハンドルの配列を作ります。ハンドルはスレッドをメインプロセスからコントロールするための値。結果を受け取ったりなんかに使います。むかーしWin32ネイティブアプリを作ってた以来ですね、ハンドルって単語を使ったの。配列の型はプレイスホルダーで指定。いつものことながら、細かいことは後でね♪という感じ。「handlesは何かの配列なんだけど、Rustは何の配列かちゃんとわかってるからね」とのこと。その後のループで各スレッドの終了をハンドリングしています。
ちなみに map
メソッドの引数はクロージャです。クロージャは |引数| {処理}
の形式で指定します。
into_iter
メソッドは、配列のメソッドでイテレーションを返すみたい。何で一々イテレータを取得?とか思ったら、mapは core::iter::Iterator
型のメソッドなんですね。
philosopers.map(|p| { thread::spawn(move || { p.eat(); }) }).collect();
試しに上記のように配列で直接 map
するコードにしてビルドしてみたら、以下のようなエラーが出ました。
src/main.rs:39:17: 43:7 note: the method `map` exists but the following trait bounds were not satisfied: `collections::vec::Vec<Philosopher> : core::iter::Iterator`, `[Philosopher] : core::iter::Iterator`
collect
はイテレータを配列に変換しています。 map
の戻り値はイテレータなんですね。この辺りの違いをちゃんと押さえてない情弱でしたorz ちなみにこれ、わざわざ collect
しないで走らせると(ビルドはちゃんと通る)並列処理になってない気がするんだけど、どうしてでしょう?
スレッドを作る
で、いよいよ本題。サブスレッドを作るには thread::spawn
関数 *1 を使います。
thread::spawn(move || { p.eat(); })
spawn
の引数はスレッドで実行される処理を指定したクロージャです。 spawn
とは卵を産むとかそういう意味なんですね。メインプロセスがスレッドを産む、というイメージでしょうか。
クロージャに指定している move
は、map
が受け取ったクロージャの引数 p
の所有権を取得するためのアノテーションです。というか ||
をorの意味だと思ってたんですが、引数リストが空という意味なんですね。プライベートな変数は完全に自分のブロック内だけで有効で、子の関係にあるブロックでも引き継がれないようです。一瞬面倒だと思ったけど、わかりやすくなりますね。ちなみに、 move
アノテーションを外してビルドすると以下のようなエラーが出ます。
src/main.rs:35:13: 35:14 note: `p` is borrowed here src/main.rs:35 p.eat(); ^ src/main.rs:34:24: 36:10 help: to force the closure to take ownership of `p` (and any other referenced variables), use the `move` keyword, as shown: src/main.rs: thread::spawn( move || { src/main.rs: p.eat(); src/main.rs: })
別の時にも思いましたけど、エラーメッセージがかなり親切な気がしますね。「これじゃね?」というサジェストをしてくれるのはなんかありがたいです。
ちなみに、 thread.spawn
にはセミコロンがありません。正しい値を移譲するため、と書いてあります。例によって別章で詳細です。
スレッドの完了を保証する
for h in handles { h.join().unwrap(); }
thread.spawn
で作成したスレッドの結果(Result型)を join
メソッドで受け取ることができます。この結果から値を取り出しているのが unwrap
です。
そろそろ英語が辛くなってきてますが
以下次号です。