Slide 1

Slide 1 text

1 1 Rust速習会2 ~Rustの苦いところハンズオン~ 2018-10-01 @Wantedlyオフィス (白金台) 原 将己 (@qnighy) 実況用ハッシュタグ: #rust_jp

Slide 2

Slide 2 text

2 2 今日の予定 • 前半: Rust特有のハマりどころに焦点を当てて説明します。 • Rustのコードを見てみよう • Rustのコスト感を知る • まずは型合わせから • 所有権とムーブ • ライフタイムと排他制御 • 継承可変性と排他制御 • 内部可変性と排他制御 • 内部可変性とスレッド安全性 • クロージャが難しくて…… • ライフタイム落ち穂拾い

Slide 3

Slide 3 text

3 3 今日の予定 • 後半: ハンズオンをやります。 • 練習問題を解きつつ、設計まわりのハマりどころを説明します。 • 関数シグネチャの設計 • 型とトレイト • データ構造の設計

Slide 4

Slide 4 text

4 4 注意事項 • Rustの文法や型などの基本事項で、他のプログラミング言語か らの類推が効く部分までは説明できていません。 • 「基本的な部分かも」と思っても、わからなかったら質問して もらえると助かります。他の人の助けにもなると思います。 • 自分で調べるときは、 TRPL から近いセクションを探して読む か、 リファレンス を見るといいでしょう。 • また、APIリファレンスは各自で参照してください。 • リンクを貼る余裕がありませんでした。 • その他、資料については前回のスライドも参照

Slide 5

Slide 5 text

5 5 Rustのコードを見てみよう

Slide 6

Slide 6 text

6 6 Rustのコードを見てみよう: 概要 • 実際にISUCON8で書いたコードを見てみる。 • RubyよりRustのほうが数倍長い。 • Rustが本質的に長い部分もあるし、非同期まわりが未成熟なた めに長くなっている部分もある。 • この章はなんとなく雰囲気が掴めればOK

Slide 7

Slide 7 text

7 7 Rustプログラミングの傾向 書きやすさ • コードは長くなる傾向にある。 • コンパイルエラー駆動で書け るので、注意力は要らない 読みやすさ • 長いので一覧性は高くない。 • コードの相互作用が明確化さ れており、追跡しやすい

Slide 8

Slide 8 text

8 8 どれくらい長くなるか…… •ISUCON8で作ったコードで例示します。 •色々な要因 • 本質的に必要な部分 • Rustの非同期まわりが未成熟なための冗長性 • Actix-Webが未成熟なための冗長性

Slide 9

Slide 9 text

9 9 参考: ISUCON8のコード片 (Ruby) def get_login_user user_id = session[:user_id] return unless user_id @login_user ||= db.xquery('SELECT id, nickname FROM users WHERE id = ?', user_id).first end get '/' do @user = get_login_user @events = get_events.map(&method(:sanitize_event)) erb :index end

Slide 10

Slide 10 text

10 10 参考: ISUCON8のコード片 (Rust 1) #[derive(Debug, Serialize)] struct UserWithNickname { id: u32, nickname: String, } #[derive(Debug, Template)] #[template(path = "index.html")] struct IndexTemplate { base_url: String, user: Option, events: Vec, }

Slide 11

Slide 11 text

11 11 参考: ISUCON8のコード片 (Rust 1) #[derive(Debug, Serialize)] struct UserWithNickname { id: u32, nickname: String, } #[derive(Debug, Template)] #[template(path = "index.html")] struct IndexTemplate { base_url: String, user: Option, events: Vec, } JSONシリアライズと テンプレートパラメーター用の構造体 テンプレート用構造体

Slide 12

Slide 12 text

12 12 参考: ISUCON8のコード片 (Rust 1) #[derive(Debug, Serialize)] struct UserWithNickname { id: u32, nickname: String, } #[derive(Debug, Template)] #[template(path = "index.html")] struct IndexTemplate { base_url: String, user: Option, events: Vec, } derive(Template): Askamaライブラリ用 derive(Serialize): serdeライブラリのシリアライズ用処理 derive(Debug): デバッグ出力用の処理をコード生成している (自分で定義することも可能)

Slide 13

Slide 13 text

13 13 参考: ISUCON8のコード片 (Rust 1) #[derive(Debug, Serialize)] struct UserWithNickname { id: u32, nickname: String, } #[derive(Debug, Template)] #[template(path = "index.html")] struct IndexTemplate { base_url: String, user: Option, events: Vec, } userは None になることがある

Slide 14

Slide 14 text

14 14 参考: ISUCON8のコード片 (Rust 2) fn get_index( (state, req, session): (State, HttpRequest, Session), ) -> Result { let base_url = { if let Some(host) = req.headers().get("Host") { format!("http://{}", host.to_str().unwrap()) } else { format!("") } }; // … }

Slide 15

Slide 15 text

15 15 参考: ISUCON8のコード片 (Rust 2) fn get_index( (state, req, session): (State, HttpRequest, Session), ) -> Result { let base_url = { if let Some(host) = req.headers().get("Host") { format!("http://{}", host.to_str().unwrap()) } else { format!("") } }; // … } リクエストに関する情報は必要なものだけ受け取る。 State はサーバー全体で共有する状態 (設定もココ) HttpRequest と Session は名前通り

Slide 16

Slide 16 text

16 16 参考: ISUCON8のコード片 (Rust 2) fn get_index( (state, req, session): (State, HttpRequest, Session), ) -> Result { let base_url = { if let Some(host) = req.headers().get("Host") { format!("http://{}", host.to_str().unwrap()) } else { format!("") } }; // … } 普通は state: State, req: HttpRequest, session: Session のように書く。 ハンドラのインターフェースを揃えるために1引数に押し込んでいる

Slide 17

Slide 17 text

17 17 参考: ISUCON8のコード片 (Rust 2) fn get_index( (state, req, session): (State, HttpRequest, Session), ) -> Result { let base_url = { if let Some(host) = req.headers().get("Host") { format!("http://{}", host.to_str().unwrap()) } else { format!("") } }; // … } Result はエラー処理用の汎用的な型。 E にはドメインごとの共通エラー型を使うことが多い (ここでは actix_web::Error を使っている)

Slide 18

Slide 18 text

18 18 参考: ISUCON8のコード片 (Rust 2) fn get_index( (state, req, session): (State, HttpRequest, Session), ) -> Result { let base_url = { if let Some(host) = req.headers().get("Host") { format!("http://{}", host.to_str().unwrap()) } else { format!("") } }; // … } HostヘッダーからURLを生成する仕組みがなかったので自作

Slide 19

Slide 19 text

19 19 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … }

Slide 20

Slide 20 text

20 20 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … } Actix-Webのセッションは文字列のマップになっている。 数値等にパースして取り出すところまでやってくれる。

Slide 21

Slide 21 text

21 21 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … } Result, Error> が返ってくる。 ? 演算子により、エラーを上に投げ返している。

Slide 22

Slide 22 text

22 22 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … } 非同期処理は Future (JSのPromiseのようなやつ) を自分で組み立てる必要がある (async/await は開発途上)

Slide 23

Slide 23 text

23 23 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … } 非同期DBアクセスはまだあまり整備されていないので、 ここは同期処理を使っている。 同期処理を別のスレッドにオフロードすることでブロッキングを防ぐ。

Slide 24

Slide 24 text

24 24 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … } このクロージャは get_index 関数終了後に呼ばれる可能性がある。 そのため、ローカル変数を普通に参照するとコンパイルエラーになる。 move をつけてムーブキャプチャーにすることで回避できる。

Slide 25

Slide 25 text

25 25 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … } ここでは、型推論をいい感じにするために戻り値型を明示している。

Slide 26

Slide 26 text

26 26 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … } .expect するとエラーをパニックに変換できる。 (ここは ? にしておくべきだった感)

Slide 27

Slide 27 text

27 27 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let mysql_pool = state.mysql_pool.clone(); let user_id = session.get::("user_id")?; let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { let mut conn = mysql_pool.get().expect("mysql connection timeout"); // … }); // … } コネクションプール(への Arc 参照) をここで一度複製している。 State は別スレッドに送れないため、これをしないと コンパイルエラーになる。

Slide 28

Slide 28 text

28 28 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { // … let user = if let Some(user_id) = user_id { conn.first_exec("SELECT nickname FROM users WHERE id = ?", (user_id,)) .map_err(error::ErrorInternalServerError)? .map(|(nickname,)| UserWithNickname { id: user_id, nickname, }) } else { None }; // … }); // … }

Slide 29

Slide 29 text

29 29 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { // … let user = if let Some(user_id) = user_id { conn.first_exec("SELECT nickname FROM users WHERE id = ?", (user_id,)) .map_err(error::ErrorInternalServerError)? .map(|(nickname,)| UserWithNickname { id: user_id, nickname, }) } else { None }; // … }); // … } user_id は Option だった。 ここでその場合分けを書いている。 (match の構文糖衣)

Slide 30

Slide 30 text

30 30 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { // … let user = if let Some(user_id) = user_id { conn.first_exec("SELECT nickname FROM users WHERE id = ?", (user_id,)) .map_err(error::ErrorInternalServerError)? .map(|(nickname,)| UserWithNickname { id: user_id, nickname, }) } else { None }; // … }); // … } user_id をシャドウイングしたので、 if の中では user_id: u32 になる。

Slide 31

Slide 31 text

31 31 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { // … let user = if let Some(user_id) = user_id { conn.first_exec("SELECT nickname FROM users WHERE id = ?", (user_id,)) .map_err(error::ErrorInternalServerError)? .map(|(nickname,)| UserWithNickname { id: user_id, nickname, }) } else { None }; // … }); // … } MySQLのエラーをActix-Webのエラーに自動変換できない。 ここでは汎用500エラーに明示的に変換している。

Slide 32

Slide 32 text

32 32 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { // … let user = if let Some(user_id) = user_id { conn.first_exec("SELECT nickname FROM users WHERE id = ?", (user_id,)) .map_err(error::ErrorInternalServerError)? .map(|(nickname,)| UserWithNickname { id: user_id, nickname, }) } else { None }; // … }); // … } first_exec なので Option<行> が返ってくる。 今回は Option::map を使って処理する。

Slide 33

Slide 33 text

33 33 参考: ISUCON8のコード片 (Rust 3) fn get_index(…) -> Result { // … let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { // … let user = if let Some(user_id) = user_id { conn.first_exec("SELECT nickname FROM users WHERE id = ?", (user_id,)) .map_err(error::ErrorInternalServerError)? .map(|(nickname,)| UserWithNickname { id: user_id, nickname, }) } else { None }; // … }); // … } 行は複数個のフィールドからなり、今回はたまたま1要素だった。 そのため、1要素のタプルが返ってくる。 1要素のタプルでは末尾カンマが必須 (Python と同じ)

Slide 34

Slide 34 text

34 34 参考: ISUCON8のコード片 (Rust 4) fn get_index(…) -> Result { // … let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { // … let events = get_events(&mut conn, true)?; Ok((user, events)) }); // … }

Slide 35

Slide 35 text

35 35 参考: ISUCON8のコード片 (Rust 4) fn get_index(…) -> Result { // … let fut = state.cpu_pool.spawn_fn(move || -> Result<_, Error> { // … let events = get_events(&mut conn, true)?; Ok((user, events)) }); // … } user, eventsだけ取り出してスレッドプールを抜ける。 コネクションはRAIIでいい感じに返却される

Slide 36

Slide 36 text

36 36 参考: ISUCON8のコード片 (Rust 5) fn get_index(…) -> Result { // … let fut = fut.map(|(user, events)| IndexTemplate { base_url, user, events, }); Ok(Box::new(fut) as Box>) }

Slide 37

Slide 37 text

37 37 参考: ISUCON8のコード片 (Rust 5) fn get_index(…) -> Result { // … let fut = fut.map(|(user, events)| IndexTemplate { base_url, user, events, }); Ok(Box::new(fut) as Box>) } さっき作った fut に map して処理を繋げている

Slide 38

Slide 38 text

38 38 参考: ISUCON8のコード片 (Rust 5) fn get_index(…) -> Result { // … let fut = fut.map(|(user, events)| IndexTemplate { base_url, user, events, }); Ok(Box::new(fut) as Box>) } セッションへの書き込みとかはスレッドプールに出せないのでここでやる。 (Actixがシングルスレッドのため) indexの場合は分ける必然性はない

Slide 39

Slide 39 text

39 39 参考: ISUCON8のコード片 (Rust 5) fn get_index(…) -> Result { // … let fut = fut.map(|(user, events)| IndexTemplate { base_url, user, events, }); Ok(Box::new(fut) as Box>) } Actix-Webの Responder にあわせるために Box に入れる

Slide 40

Slide 40 text

40 40 えっ……長くない? •えっ……長くない?

Slide 41

Slide 41 text

41 41 えっ……長くない?

Slide 42

Slide 42 text

42 42 コンパイルエラー駆動開発! 適当に書く とりあえず コンパイルする 適当に直す 実行する ちょっと考える コンパイルエラー 実行時エラー / バグ OK OK

Slide 43

Slide 43 text

43 43 エラーが親切な例 ムーブキャプチャーの必要があるとき

Slide 44

Slide 44 text

44 44 ちょっと考える必要がある例 Send じゃないものを他スレッドに送ろうとした

Slide 45

Slide 45 text

45 45 ちょっと考える必要がある例 Send じゃないものを他スレッドに送ろうとした • Rc はスレッド非セーフな参照カウンタ • Rc なんて使ってないぞ……? • →よく見るとActix-Webの内部で使われている • Actix-Webの State はスレッドプールに持ち込めない ので必要な部分だけ取り出して送る必要があるんだ な!

Slide 46

Slide 46 text

46 46 ちょっと考える必要がある例 最後に Box する必要があるのにしていない ↑とんちんかんなnoteが出ている

Slide 47

Slide 47 text

47 47 ちょっと考える必要がある例 最後に Box する必要があるのにしていない • Responder は非同期でもいいはずなので Future を取れな いのはおかしい • 仮説1: Item/Error が一致していない? • 仮説2: 実は Future に対して実装されていない? • Responder のドキュメントを読むと、 Box にしか実装されていないことが判明

Slide 48

Slide 48 text

48 48 コンパイルエラー駆動とは言うけれど • はまりがちな罠は先にわかっておいたほうがよい • 順番に紹介します!

Slide 49

Slide 49 text

49 49 Rustのコスト感を知る ライフタイムに入る前に、コスト感を知っておきましょう

Slide 50

Slide 50 text

50 50 Rustのコスト感を知る: 概要 • 実行時コストがRustの設計に深く関わっている、という感覚を 掴む。 • 実行時コストのかかるものがオプトインになっている傾向があ る。

Slide 51

Slide 51 text

51 51 Rustのコスト感 x = [1, 2, 3] このPythonコードをRustに翻訳すると……

Slide 52

Slide 52 text

52 52 Rustのコスト感 let mut x = Arc::new(Mutex::new( vec![1, 2, 3])); だいたいこんな感じになる。 普通はここまで重装備である必要はない

Slide 53

Slide 53 text

53 53 Rustのコスト感 let mut x = Arc::new(Mutex::new( vec![1, 2, 3])); Mutex は動的に排他制御する。 →いらないなら外す

Slide 54

Slide 54 text

54 54 Rustのコスト感 let mut x = Arc::new(vec![1, 2, 3]); Arc は生存期間を動的に制御する。 →いらないなら外す

Slide 55

Slide 55 text

55 55 Rustのコスト感 let mut x = vec![1, 2, 3]; Vec は動的に長さを変えられる。 →いらないなら外す

Slide 56

Slide 56 text

56 56 Rustのコスト感 let mut x = [1, 2, 3]; mut は書き換え可能であることを示す →いらないなら外す (mutに実行時コストはない)

Slide 57

Slide 57 text

57 57 Rustのコスト感 let x = [1, 2, 3]; Rustでは軽いほうがデフォルト。 必要になってから実行時コストを オプトインしていく。

Slide 58

Slide 58 text

58 58 Rustのコスト感 let x = [1, 2, 3]; 言語が実行時コストを教えてくれる →普段から気にする必要はない

Slide 59

Slide 59 text

59 59 まずは型合わせから

Slide 60

Slide 60 text

60 60 まずは型合わせから: 概要 • はまりがちなコンパイルエラーのうち、単純な型違いを埋める。 • Option, Result, 参照型のつけ外しの方法を知る。

Slide 61

Slide 61 text

61 61 Option/Result と和解せよ • ML系関数型言語ではおなじみの概念 • Option: Some(t), None のどちらか • Result: Ok(t), Err(e) のどちらか • 付属のメソッドを使ってもいいし、自力でパターンマッチを書 いてもOK ※汎用のEitherについてはeither crateを参照 ※Option/Resultの内容に応じてcontinue/break/returnしたい ときは自力でパターンマッチするのが定石

Slide 62

Slide 62 text

62 62 Option/Result と和解せよ Option Result res.ok() opt.ok_or_else() T opt? opt.unwrap() opt.expect() opt.unwrap_or_else() res? res.unwrap() res.expect() res.unwrap_or_else() opt.map() res.map() res.map_err() ※ok はエラー情報を捨てるので好まれない ※unwrap, expect はリカバリを諦める関 数なので濫用厳禁 ※opt?, res? はエラーを投げ返すだけなの で、Option/Resultを返す関数内でしか 使えない

Slide 63

Slide 63 text

63 63 参照型の大原則 • &T, &mut T は独立した型であり、型合わせのときは他の型と 同じように振る舞う • たとえば…… • Option<&'a T> と &'a Option は別の型である。 • Option<&'a T> と Option<&'a mut T> は別の型である。 • Option<&'a &'b T> と Option<&'a T> は別の型である。 この原則に対する例外はよく見られる。 • 自動参照外し • 自動参照 (特にメソッド記法のレシーバー) • 型強制と再借用

Slide 64

Slide 64 text

64 64 値と参照の三つ巴 • &T, &mut T, T の三つ巴 (または最初の2つ) で並立しているAPI は結構ある たとえば…… • Fn, FnMut, FnOnce • Deref, DerefMut, -- • Index, IndexMut, -- • Borrow, BorrowMut, -- • AsRef, AsMut, Into • iter(), iter_mut(), into_iter() • split_at(), split_at_mut(), -- • 式のモード (イミュータブル左辺値、ミュータブル左辺値、右辺値) • パターンのモード (イミュータブル左辺値、ミュータブル左辺値、右辺値)

Slide 65

Slide 65 text

65 65 値と参照の三つ巴 &'a T &'a mut T p T *p p.clone() *p p.clone() mem::replace(p, y) &x &mut x ※型強制

Slide 66

Slide 66 text

66 66 値と参照の三つ巴 (unsized版) 基本形 &'a T &'a mut T T トレイトオブジェクト &'b dyn Tr + 'a &'b mut dyn Tr + 'a Box 配列 &'a [T] &'a mut [T] Box<[T]> [T; n] &'a Vec &'a mut Vec Vec 文字列 &'a str &'a mut str Box &'a String &'a mut String String • 配列、文字列、トレイトオブジェクトの三つ巴は少し異なる • Unsized typeはポインタ経由でしか扱えないので、 Box で包む (fat pointer になる) • Vec, String は可変長な Box である。(長さと容量を別々に持つ) • 長さフィールドをケチる理由があまりないため Box<[T]>, Box はあまり使われない。 • UTF-8文字列をバイト数を変えずに置き換える需要がないため、&mut str はほとんど使われない。 • イミュータブル参照では可変長である意味はないので &Vec, &String は使わない。 • [T; n] はコンパイル時に長さが決まる (文字列に対応する型はない)

Slide 67

Slide 67 text

67 67 値と参照の三つ巴 (文字列版) &'a str &'a mut String p String p.to_string() p.to_string() mem::replace(p, y) &x &mut x ※型強制

Slide 68

Slide 68 text

68 68 値と参照の三つ巴(?) (配列版) &'a [T] &'a mut [T] p Vec p.to_vec() p &x &mut x ※型強制 &'a mut Vec &mut x p.clone() mem::replace(p, y) p.to_vec() ※型強制

Slide 69

Slide 69 text

69 69 参照を見落としがちなところ • Q. x の型はなんでしょう? let v: Vec = vec![1, 2, 3]; for x in v.iter() { // … }

Slide 70

Slide 70 text

70 70 参照を見落としがちなところ • Q. x の型はなんでしょう? let v: Vec = /* … */; for x in v.iter() { // … } 「とっても大きな木」 のように、複製にコストがかかるものだったら……?

Slide 71

Slide 71 text

71 71 参照を見落としがちなところ • Q. x の型はなんでしょう? let v: Vec> = /* … */; for x in v.iter() { // … } 「mutexガードされたコネクション」 のように、複製できないものだったら……?

Slide 72

Slide 72 text

72 72 参照を見落としがちなところ • Q. x の型はなんでしょう? let v: Vec = vec![1, 2, 3]; for x in v.iter() { // … } ここには「要素への参照」が入ってくる!

Slide 73

Slide 73 text

73 73 参照を見落としがちなところ • Q. x の型はなんでしょう? let v: Vec = vec![1, 2, 3]; for x in v.iter() { // … } ちなみに、要素への参照ではなく要素で受けるには…… • for x in x.iter().cloned() とする (要素型が Clone のとき使える) • for &x in v.iter() とする (要素型が Copy のとき使える) • for x in v.drain(..) とする (いつでも使える。配列は空になる) • for x in v (あるいは v.into_iter()) とする (いつでも使える。配列ごと手放すことになる)

Slide 74

Slide 74 text

74 74 秘技!自動参照外し let s: String = …; let s = s.trim(); この s は &str 型になっている!

Slide 75

Slide 75 text

75 75 秘技!自動参照外し let s: String = …; let s = s.trim(); このようなパターンはよく見られるが、実際の仕組みは以下の通り。 • s.trim() のようなメソッド記法はドットを使わない方法で置き換えられる。 • 今回は str::trim の糖衣。 • この第一引数は s, &s, &mut s のいずれかの形が採用される。 • 今回は str::trim(&s) となる。 • & を使った位置では任意個の * がついたものとみなされる。 • 今回は str::trim(&*s) となる。 • *x はオーバーロード可能で *Deref::deref(&s) に脱糖される。 • str::trim(&*String::deref(&s)) となる。

Slide 76

Slide 76 text

76 76 参照を見落としがちなところ • Q. x の型はなんでしょう? let v: Vec = vec![1, 2, 3]; for x in v.iter() { // … } この v も実は &v を渡している!

Slide 77

Slide 77 text

77 77 所有権とムーブ

Slide 78

Slide 78 text

78 78 所有権とムーブ: 概要 • 所有権は何らかの唯一性を保障するのに使える仕組みで、その 内実は初期化チェックの逆にすぎない。 • Rustにおけるムーブは、所有権が絡む以外はコピーとほぼ同じ である。

Slide 79

Slide 79 text

79 79 所有権 • 問題: 同じものが複数あると困ることがある (詳しくは後述) • 解答: 未初期化チェックを応用しよう

Slide 80

Slide 80 text

80 80 Javaの未初期化チェック • コンパイル時に検査される int x; System.out.println("x = " + x); 未初期化!

Slide 81

Slide 81 text

81 81 Rustの未初期化チェック • Rustも同じ let x: u32; println!("x = {}", x); 未初期化!

Slide 82

Slide 82 text

82 82 Rustのムーブ検査 • よくある最小例 let x = String::from("Hello!"); let y = x; println!("x = {}", x); ムーブ済みなのでエラー!

Slide 83

Slide 83 text

83 83 Rustのムーブ検査 • 初期化とムーブアウトは逆の操作 let x: String; println!("x = {}", x); x = String::from("Hello!"); println!("x = {}", x); let y = x; println!("x = {}", x); 初期化 ムーブ OK × ×

Slide 84

Slide 84 text

84 84 ムーブはほとんどコピー • Rustの「コピー」は単純コピー 0x00000000000378d0 未初期化 0x00000000000378d0 0x00000000000378d0 ※Rust用語では、単純コピーをコピーと呼び、複雑な処理が必要なものはクローンと呼ばれる。 これはC++の用語法とは異なる。

Slide 85

Slide 85 text

85 85 ムーブはほとんどコピー • Rustの「ムーブ」も単純コピー 0x00000000000378d0 未初期化 0x00000000000378d0 (ムーブ済み無効) 0x00000000000378d0 ※C++のムーブは単純コピーではない。 ※条件付きムーブなどではdrop flagが生成される場合がある。 その場合は単純コピーに加えて、フラグを立てる処理が加わる。

Slide 86

Slide 86 text

86 86 参照からはムーブできない • 参照からコピーはできる fn take(x: &i32) -> i32 { *x }

Slide 87

Slide 87 text

87 87 参照からはムーブできない • 参照からムーブはできない fn take(x: &String) -> String { *x } エラー! コピーになるか、ムーブになるかは組み込みの Copy トレイトで制御されている。

Slide 88

Slide 88 text

88 88 参照からはムーブできない • mut参照でもダメ! • 参照は中身を入れて返さないといけないため、空にできない fn take(x: &mut String) -> String { *x } エラー!

Slide 89

Slide 89 text

89 89 参照からはムーブできない • mut参照でもダメ! • 参照は中身を入れて返さないといけないため、空にできない fn take(x: &mut String) -> String { *x } エラー! mut参照の場合、代わりの中身を入れることでムーブできる。そのためには標準ライブラリの replace 関数を使う。 初期化/未初期化の状態遷移が複雑なときは、動的な制御にオプトすることもできる。そのためには Option を使い、 None を未初期化、 Some を初期化済みとみなす。 Option::replace や Option::take メソッドを補助として用 いることができる。 この目的で Option が必要になるのは、 Future の実装など特殊なライブラリが多い。アプリケーションコードでこ のパターンが必要になることはあまり多くないと思われる。

Slide 90

Slide 90 text

90 90 困ったらクローン • クローンはディープコピーを行う (Arc/Rc に対しては浅いコ ピー) fn take(x: &String) -> String { x.clone() }

Slide 91

Slide 91 text

91 91 困ったらクローン • ハンドル系の値はクローンできないこともある fn take(x: &File) -> File { x.clone() } エラー! 上記のコードをコンパイルすると、本来あるべきエラー(cloneできない)ではなく、型エラーが表示される。 これはメソッド解決のルールが悪さをしている。 File がクローンできないが、 &File はクローンできるため、そち らだと思ってコンパイルが続行された結果、型エラーになる。 また、 File は通常の意味ではクローンできないが、 dup 系のシステムコールで複製する try_clone がある。

Slide 92

Slide 92 text

92 92 困ったらクローン • Arc/Rc に包めば浅いコピーになるため、クローンできる fn take(x: &Arc) -> Arc { x.clone() } OK! 同じハンドルを指している Arc/Rc に包むときは、内部可変性が一緒に必要になることが多い。 (後述) ライブラリによっては、ハンドルを Arc/Rc で包んだ状態のものが提供されていることがある。これらはクローンで き、それらは同じものを参照している。

Slide 93

Slide 93 text

93 93 ライフタイムと排他制御

Slide 94

Slide 94 text

94 94 ライフタイムと排他制御: 概要 • ライフタイムはスタック領域のためと思われがちだが、静的な 排他制御をするというもう一つの役割がある。 • 参照の排他性は時間分割か空間分割によって保障される。ライ フタイムは時間分割を正当化するという重要な役割がある。 • 時間分割が強制されることにより、ある種の曖昧なコードを防 ぐことができる。これは一方で、はまりやすいコンパイルエ ラーの一種でもある。

Slide 95

Slide 95 text

95 95 ライフタイムの存在目的 • スタック領域を安全に使うため • 静的な排他制御のため

Slide 96

Slide 96 text

96 96 スタック領域の安全性 • 安全でない例 (コンパイルエラー) fn foo() -> &i32 { let x = 42; &x }

Slide 97

Slide 97 text

97 97 スタック領域の安全性 • 安全でない例 (コンパイルエラー) fn foo() -> &i32 { let x = 42; &x } foo 用のスタックフレーム上に 42が置かれる この参照は foo 内でしか有効ではない! 関数外に返せてしまうと問題になる

Slide 98

Slide 98 text

98 98 スタック領域の安全性 • 安全でない例 (コンパイルエラー) fn foo() -> &i32 { let x = 42; &x } 多くの言語では、ユーザーが扱えるポインタはGC管理されており、常にヒープを指すので、このような管 理は要らない。そのかわりヒープ管理とGCのコストが追加される。 GoやML Kitなどの一部の言語には、安全だとわかっているときだけ自動的にスタックに確保するような仕 組みがある。 Rustがそのような自動化をしない理由はいくつか考えられる。たとえば、RustはCと同様、ヒープへの依存 を切り離せるように設計されている。そのため、ヒープがデフォルトになる仕組みが言語に入ることはあま り考えられない。

Slide 99

Slide 99 text

99 99 静的な排他制御のためのライフタイム • Q. src と dst が同じだったら何が起こる? fn append(src: &Vec, dst: &mut Vec) { for &x in src { dst.push(x); } }

Slide 100

Slide 100 text

100 100 静的な排他制御のためのライフタイム • Q. src と dst が同じだったら何が起こる? • A. src と dst が同じになることはない fn append(src: &Vec, dst: &mut Vec) { for &x in src { dst.push(x); } }

Slide 101

Slide 101 text

101 101 静的な排他制御のためのライフタイム • Q. src と dst が同じだったら何が起こる? • A. src と dst が同じになることはない fn append(src: &Vec, dst: &mut Vec) { for &x in src { dst.push(x); } } コンパイラ: コーナーケースを考えなくてよ いので嬉しい! プログラマ: コーナーケースを考えなくてよ いので嬉しい!

Slide 102

Slide 102 text

102 102 静的な排他制御のためのライフタイム • 呼んでみよう! fn append(src: &Vec, dst: &mut Vec) { for &x in src { dst.push(x); } } fn main() { let mut v = vec![1, 2, 3]; append(&v, &mut v); println!("{:?}", v); }

Slide 103

Slide 103 text

103 103 復習: 排他制御のルール • 同じ時刻に、同じ領域に対して、 • 書き込み・書き込み→NG • 書き込み・読み取り→NG • 読み取り・読み取り→OK

Slide 104

Slide 104 text

104 104 図でわかるRustの排他制御 まずは何となく眺めてください

Slide 105

Slide 105 text

105 105 図でわかるRustの排他制御 • 凡例 書き込み権限 読み取り権限 期限あり→ 期限なし→ 内部可変性

Slide 106

Slide 106 text

106 106 図でわかるRustの排他制御 • 所有権・借用は3つの方法で分割できる 時間分割 空間分割 共有分割 時間経過 メモリ空間

Slide 107

Slide 107 text

107 107 図でわかるRustの排他制御 • 変数を宣言した瞬間 時間経過 メモリ空間 変数定義 変数 x

Slide 108

Slide 108 text

108 108 図でわかるRustの排他制御 • &mut 参照の取得 = 時間分割 時間経過 メモリ空間 変数定義 変数 x &mut x 借用の期限

Slide 109

Slide 109 text

109 109 図でわかるRustの排他制御 • & 参照の取得 = 共有分割 (+時間分割) 時間経過 メモリ空間 変数定義 変数 x &x 借用の期限

Slide 110

Slide 110 text

110 110 図でわかるRustの排他制御 • Arc = 共有分割 時間経過 メモリ空間 変数定義 Arc::new(…)

Slide 111

Slide 111 text

111 111 図でわかるRustの排他制御 • &mut 参照の空間分割 (構造体の分割借用、split_at_mut) 時間経過 メモリ空間 変数定義 変数 x 借用の期限 &mut x.0 &mut x.1

Slide 112

Slide 112 text

112 112 図でわかるRustの排他制御 • 所有権の空間分割 (構造体の分割、drain) 時間経過 メモリ空間 変数定義 x.0 借用の期限 x.1 &mut x

Slide 113

Slide 113 text

113 113 図でわかるRustの排他制御 • Mutex: 枠を共有し、中身を動的に排他制御する 時間経過 メモリ空間 変数定義 Mutex変数 x &x 借用の期限 x.lock()

Slide 114

Slide 114 text

114 114 図でわかるRustの排他制御 何となく、雰囲気だけでもわかりましたか? では具体例を見てみましょう

Slide 115

Slide 115 text

115 115 例: セルフappendが呼べない理由 append(&v, &mut v); 時間経過 メモリ空間 変数定義 変数 v &mut v 借用の期限 &v

Slide 116

Slide 116 text

116 116 例: ループ内での自己改変 let mut v = vec![8, 9, 10]; for &x in &v { if x > 0 { v.push(x / 2); } }

Slide 117

Slide 117 text

117 117 例: ループ内での自己改変 let mut v = vec![8, 9, 10]; for &x in &v { if x > 0 { v.push(x / 2); } } ループの間 v を借りている 時間経過 書き込みがイテレーターの読み取りと競合する

Slide 118

Slide 118 text

118 118 例: ループ内での自己改変 • そもそも、このコードは期待する実行結果が不明瞭 • パターン1: [8, 9, 10, 4, 4, 5] (伸びた分のループは実行されない) • パターン2: [8, 9, 10, 4, 4, 5, 2, 2, 2, 1, 1, 1, 0, 0, 0] (伸びた分のループも実行される)

Slide 119

Slide 119 text

119 119 例: ループ内での自己改変 • len の更新を無視してループしたい場合 (1) let mut v = vec![8, 9, 10]; for x in v.clone() { if x > 0 { v.push(x / 2); } }

Slide 120

Slide 120 text

120 120 例: ループ内での自己改変 • len の更新を無視してループしたい場合 (1) let mut v = vec![8, 9, 10]; for x in v.clone() { if x > 0 { v.push(x / 2); } } v をループ開始前に複製してしまう。 時間経過

Slide 121

Slide 121 text

121 121 例: ループ内での自己改変 • len の更新を無視してループしたい場合 (2) let mut v = vec![8, 9, 10]; for i in 0..v.len() { let x = v[i]; if x > 0 { v.push(x / 2); } }

Slide 122

Slide 122 text

122 122 例: ループ内での自己改変 • len の更新を無視してループしたい場合 (2) let mut v = vec![8, 9, 10]; for i in 0..v.len() { let x = v[i]; if x > 0 { v.push(x / 2); } } v をループ開始時に一瞬だけ借りている。 一度覚えた長さをずっと使う。 時間経過

Slide 123

Slide 123 text

123 123 例: ループ内での自己改変 • len の更新を反映してループしたい場合 let mut v = vec![8, 9, 10]; let mut i = 0; while i < v.len() { let x = v[i]; if x > 0 { v.push(x / 2); } i += 1; }

Slide 124

Slide 124 text

124 124 例: ループ内での自己改変 • len の更新を反映してループしたい場合 let mut v = vec![8, 9, 10]; let mut i = 0; while i < v.len() { let x = v[i]; if x > 0 { v.push(x / 2); } i += 1; } v の長さを毎回取りに行く 時間経過

Slide 125

Slide 125 text

125 125 他の言語では…… • できるだけ同じ構造のコードを書いて実験してみた • Ruby: [8, 9, 10, 4, 4, 5, 2, 2, 2, 1, 1, 1, 0, 0, 0] • Python: [8, 9, 10, 4, 4, 5, 2, 2, 2, 1, 1, 1, 0, 0, 0] • Java: java.util.ConcurrentModificationException • C++: [8, 9, 10, 4, 5] (おそらく未定義動作) • Go: [8, 9, 10, 4, 4, 5] • JavaとGoはこのケースを意識的にハンドルしてる感じがある

Slide 126

Slide 126 text

126 126 継承可変性と排他制御

Slide 127

Slide 127 text

127 127 継承可変性と排他制御: 概要 • Rustのイミュータビリティーは継承される。これは排他制御の 原理から理解することができる。 • イミュータビリティーが継承されることで、ネストしたデータ 構造の意図しない書き換えを防止できる。

Slide 128

Slide 128 text

128 128 継承可変性と内部可変性 フィールドや参照先に 可変性が継承される Rustではこっちが原則 継承可変性 不変な構造体の内側に 可変な値をとれる Rustでは内部可変性を オプトインする 内部可変性 (inherited mutability) (interior mutability)

Slide 129

Slide 129 text

129 129 例: グラフオブジェクト • グラフをインメモリの隣接リストで保持するオブジェクトを考 える。 • AddVertex() -> Integer • AddEdge(a: Integer, b: Integer) • GetGraph() -> Array>

Slide 130

Slide 130 text

130 130 Rubyの場合 class Graph attr_reader :graph def initialize; @graph = []; end def add_vertex; @graph << []; @graph.len - 1; end def add_edge(a, b); @graph[a] << b; @graph[b] << a; end end

Slide 131

Slide 131 text

131 131 Pythonの場合 class Graph(object): def __init__(self): self._graph = [] def add_vertex(self): self._graph.append([]) return len(self._graph) - 1 def add_edge(self, a, b): self._graph[a].append(b) self._graph[b].append(a) @property def graph(self): return self._graph

Slide 132

Slide 132 text

132 132 例: グラフオブジェクト • グラフをインメモリの隣接リストで保持するオブジェクトを考 える。 • AddVertex() -> Integer • AddEdge(a: Integer, b: Integer) • GetGraph() -> Array> • 追加の要件: GetGraphはイミュータブルなビューを返す

Slide 133

Slide 133 text

133 133 RubyやPythonでは…… • 方法1: ディープコピーする • 方法2: ディープコピーをキャッシュする • 方法3: 専用のビュークラスを作る (再帰的にビューを返す)

Slide 134

Slide 134 text

134 134 Rustの場合 struct Graph { graph: Vec>, } impl Graph { pub fn new() -> Self { Self { graph: Vec::new() } } pub fn add_vertex(&mut self) { self.graph.push(Vec::new()); } pub fn add_edge(&mut self, a: usize, b: usize) { self.graph[a].push(b); self.graph[b].push(a); } pub fn graph(&self) -> &[Vec] { &self.graph } } イミュータブルなスライス

Slide 135

Slide 135 text

135 135 Rustの場合 struct Graph { graph: Vec>, } impl Graph { pub fn new() -> Self { Self { graph: Vec::new() } } pub fn add_vertex(&mut self) { self.graph.push(Vec::new()); } pub fn add_edge(&mut self, a: usize, b: usize) { self.graph[a].push(b); self.graph[b].push(a); } pub fn graph(&self) -> &[Vec] { &self.graph } } 内側も自動的にイミュータブルになる (継承可変性)

Slide 136

Slide 136 text

136 136 再掲: 排他制御のルールと参照 • 同じ時刻に、同じ領域に対して、 • 書き込み・書き込み→NG • 書き込み・読み取り→NG • 読み取り・読み取り→OK • Rustの参照: • &T は共有参照 → 共有されているので(原則)読み取り専用 • &mut T は排他参照 → 排他的なので読み書き可能

Slide 137

Slide 137 text

137 137 参照の参照 &mut &mut T &mut T T &mut T T &T T &&mut T &mut T T &mut &T &T T &&T &T T

Slide 138

Slide 138 text

138 138 内部可変性と排他制御

Slide 139

Slide 139 text

139 139 内部可変性と排他制御: 概要 • 継承可変性では不十分な場合には、共有書き換えの仕組みをオ プトインすることができる。これを内部可変性という。 • 内部可変性は、動的な排他制御として一貫して理解することが できる。

Slide 140

Slide 140 text

140 140 継承可変性と内部可変性 フィールドや参照先に 可変性が継承される Rustではこっちが原則 継承可変性 不変な構造体の内側に 可変な値をとれる Rustでは内部可変性を オプトインする 内部可変性 (inherited mutability) (interior mutability)

Slide 141

Slide 141 text

141 141 例: 一意IDを振る君 • UUIDやsnowflakeのような一意IDを振るプロセス内サービスを 考える

Slide 142

Slide 142 text

142 142 例: 一意IDを振る君 • だいたいこんな感じのラッパー型を作る #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Id(u32); impl Id { pub fn id(&self) -> u32 { self.0 } }

Slide 143

Slide 143 text

143 143 例: 一意IDを振る君 • 一意IDを振る君の構造体を定義する #[derive(Debug)] pub struct GenId { next: u32, } impl GenId { pub fn new() -> Self { Self { next: 0, } } }

Slide 144

Slide 144 text

144 144 例: 一意IDを振る君 • 一意IDを振る君の共有方法はいくつかある • 親玉が生成した &GenId を持ち回す • Arc に包んで Arc として複製していく • lazy_static! でグローバル変数として共有する • いずれにしても、 GenId は共有されるというのがミソ

Slide 145

Slide 145 text

145 145 例: 一意IDを振る君 • 共有していても、 next は書き換えたい #[derive(Debug)] pub struct GenId { next: u32, } impl GenId { pub fn new() -> Self { Self { next: 0, } } } 共有中は書き換えられない

Slide 146

Slide 146 text

146 146 例: 一意IDを振る君 • Mutex などで内部可変性を導入する #[derive(Debug)] pub struct GenId { next: Mutex, } impl GenId { pub fn new() -> Self { Self { next: Mutex::new(0), } } } 共有中は書き換えられない Rustの Mutex は「ミューテックス本体」と「それによって 守られるデータ」の対になっている。 Mutex の中に入れれば、ロックが必要になる。 Mutex の外に出せば、共有書き込みはできない。 どちらにしても、危険な動作はできない。

Slide 147

Slide 147 text

147 147 例: 一意IDを振る君 • 使うときはロックを取得する impl GenId { pub fn gen(&self) -> Id { let mut next = self.next.lock().unwrap(); let id = Id(*next); *next += 1; id } } ロックを取得 ここで next がドロップされる。 ロックも自動的に解放される。

Slide 148

Slide 148 text

148 148 例: 一意IDを振る君 impl GenId { pub fn gen(&self) -> Id { let mut next = self.next.lock().unwrap(); let id = Id(*next); *next += 1; id } } ロック中は &mut 参照のように扱うことができる

Slide 149

Slide 149 text

149 149 例: 一意IDを振る君 • ロックの粒度 impl GenId { pub fn gen(&self) -> Id { let mut next = self.next.lock().unwrap(); let id = Id(*next); drop(next); let mut next = self.next.lock().unwrap(); *next += 1; id } } わざわざこう書く人はたぶんいないが…… こうすると一旦ロックを解放するので不整合が起きる

Slide 150

Slide 150 text

150 150 例: 一意IDを振る君 • ロックの粒度 impl GenId { pub fn gen(&self) -> Id { let mut next = self.next.lock().unwrap(); let id = Id(*next); drop(next); let mut next = self.next.lock().unwrap(); *next += 1; id } } 逆に言えば、ロックを握っている間はちゃんと専有 しているので、知らない変更が紛れることはない

Slide 151

Slide 151 text

151 151 内部可変性とスレッド安全性

Slide 152

Slide 152 text

152 152 内部可変性とスレッド安全性: 概要 • 継承可変性と異なり、内部可変性は実装によってはスレッド安 全ではない。 • Rustでは、スレッド安全な実装とシングルスレッド実装を選択 できる。 • シングルスレッド実装を選んでも、危険なコードは未然に防止 される。

Slide 153

Slide 153 text

153 153 継承可変性と内部可変性 フィールドや参照先に 可変性が継承される Rustではこっちが原則 継承可変性 不変な構造体の内側に 可変な値をとれる Rustでは内部可変性を オプトインする 内部可変性 (inherited mutability) (interior mutability)

Slide 154

Slide 154 text

154 154 継承可変性とスレッド安全性 フィールドや参照先に 可変性が継承される Rustではこっちが原則 継承可変性 不変な構造体の内側に 可変な値をとれる Rustでは内部可変性を オプトインする 内部可変性 (inherited mutability) (interior mutability) 静的な排他制御 ↓ 自然にスレッド安全になる 動的な排他制御 ↓ スレッド安全性も実行時コスト

Slide 155

Slide 155 text

155 155 継承可変性とスレッド安全性 フィールドや参照先に 可変性が継承される Rustではこっちが原則 継承可変性 不変な構造体の内側に 可変な値をとれる Rustでは内部可変性を オプトインする 内部可変性 (inherited mutability) (interior mutability) 静的な排他制御 ↓ 自然にスレッド安全になる 動的な排他制御 ↓ スレッド安全性も実行時コスト

Slide 156

Slide 156 text

156 156 内部可変性の亜種たち 機能 Atomic* Cell Mutex RwLock RefCell スレッド安全 シングルスレッド 任意長 ロックが取れる 共有ロックが取れる ※これとは別の実装の Mutex が parking_lot クレートに存在している。

Slide 157

Slide 157 text

157 157 Arc と Rc • Arc と Rc は内部可変性を作るのには使えない。 • しかし、参照カウンタのために内部可変性を使っているため、 スレッドセーフな Arc とシングルスレッド用の Rc がある。 ※単一のロックで参照カウンタと参照先の両方をロックするような実装も 考えられる。もちろん、長所と短所がある。 Rustでは粒度を細かくするほうを選択したようだ。

Slide 158

Slide 158 text

158 158 スレッド安全性を守る • 例: rayon を使って並列に何かを計算してみる extern crate rayon; use rayon::prelude::*; fn main() { let sum = (0u64..1000).into_par_iter().map(|n| n * n).sum::(); println!("sum = {}", sum); }

Slide 159

Slide 159 text

159 159 スレッド安全性を守る • 例: パフォーマンスカウンタ的なことをやりたくなった fn main() { let mut counter = 0; let sum = (0u64..1000) .into_par_iter() .map(|n| { counter += 1; n * n }) .sum::(); println!("sum = {}", sum); println!("counter = {}", counter); }

Slide 160

Slide 160 text

160 160 スレッド安全性を守る • 例: パフォーマンスカウンタ的なことをやりたくなった fn main() { let mut counter = 0; let sum = (0u64..1000) .into_par_iter() .map(|n| { counter += 1; n * n }) .sum::(); println!("sum = {}", sum); println!("counter = {}", counter); } 普通の(直列)イテレーターならこれでコンパイルできる。 (順番に実行されるから、環境をmutキャプチャー可能)

Slide 161

Slide 161 text

161 161 スレッド安全性を守る • 例: パフォーマンスカウンタ的なことをやりたくなった fn main() { let mut counter = 0; let sum = (0u64..1000) .into_par_iter() .map(|n| { counter += 1; n * n }) .sum::(); println!("sum = {}", sum); println!("counter = {}", counter); } 並列イテレーターではエラーになってしまう。 (counter への参照を各所で共有するため)

Slide 162

Slide 162 text

162 162 スレッド安全性を守る • 例: 内部可変性を導入する fn main() { let counter = Cell::new(0); let sum = (0u64..1000) .into_par_iter() .map(|n| { counter.set(counter.get() + 1); n * n }) .sum::(); println!("sum = {}", sum); println!("counter = {}", counter.get()); } スレッド安全ではない! コンパイルエラーになる。

Slide 163

Slide 163 text

163 163 スレッド安全性を守る • 例: スレッド安全な内部可変性を導入する fn main() { let counter = AtomicUsize::new(0); let sum = (0u64..1000) .into_par_iter() .map(|n| { counter.fetch_add(1, atomic::Ordering::SeqCst); n * n }) .sum::(); println!("sum = {}", sum); println!("counter = {}", counter.load(atomic::Ordering::SeqCst)); } スレッド安全! ※本当は AtomicU32 を使いたいが、これは移植性に関する仕様が定まって いないためnightlyでしか使えない。

Slide 164

Slide 164 text

164 164 Send と Sync • スレッド安全性は Send と Sync というマーカートレイトに よって再帰的に検査される。

Slide 165

Slide 165 text

165 165 Send と Sync • Send: 全体を別スレッドに送ることができる。 Thread 1 Thread 2 T

Slide 166

Slide 166 text

166 166 Send と Sync • Send: 全体を別スレッドに送ることができる。 Thread 1 Thread 2 T

Slide 167

Slide 167 text

167 167 Send と Sync • Sync: 別スレッドと共有することができる。 Thread 1 Thread 2 T &T &T

Slide 168

Slide 168 text

168 168 Send と Sync • Sync: 別スレッドと共有することができる。 Thread 1 Thread 2 T &T &T このとき、 T が Sync であるという。 (&T は Send)

Slide 169

Slide 169 text

169 169 Mutex の特殊能力 • Mutex は Send を Send + Sync に格上げできる。 • RwLock にこの能力はない ※ RwLock ではなく Mutex を使う他の理由として、書き込み飢餓状態を避 けたいというケースも考えられる。

Slide 170

Slide 170 text

170 170 スレッド保証をどう選ぶか • アプリケーションコードでは、シングルスレッドで始めて、 困ってからスレッド安全な実装に切り替えてもよい。 • ライブラリでは、ユーザーが Send を必要としているかどうか ちゃんと考えてほしいかも…… • ひとたび Rc を使ってしまうと、その値はどう頑張っても他のスレッ ドに逃げられない。

Slide 171

Slide 171 text

171 171 クロージャが難しくて…… 苦労じゃ

Slide 172

Slide 172 text

172 172 クロージャが難しくて……: 概要 • クロージャは関数と違ってキャプチャーをするので、キャプ チャーと所有権の関わりでハマる可能性がある。 • 実際のところ、使うときは move をつけるかつけないかくらい しか判断するところはない。たいてい、クロージャが長生きす る必要があるときに move をつける。 • それに加えて、クロージャのインターフェースを設計するとき は、 FnOnce/FnMut/Fn を選ぶ必要がある。 • 外側の制御フローへの干渉はできない。

Slide 173

Slide 173 text

173 173 クロージャの構文 • パイプ2本を前置すると、クロージャになる。 • 引数の型は省略できる。明示するときはこんな感じ。 • 戻り値の型を明示するときは、 {} が必要。 • move を前置できる(後述)。これはorではない。 |x| x * a |x: i32, y: i32| x * a + y || -> i32 { 42 } move || x

Slide 174

Slide 174 text

174 174 Rustにおけるクロージャと関数 • どちらも、定義ごとに異なる型を持つ。 • どちらも、 Fn* トレイトを実装し、 f() 構文で呼び出せる。 • 関数は多相だが、クロージャは多相ではない。 • クロージャは再帰できない。 • 関数は外のローカル変数を参照できない。クロージャは外の ローカル変数を参照できる(キャプチャー)。 ※C/C++の関数は定義ごとに異なる型を持つわけではない。 ※C++は多相クロージャを持つし、クロージャによる再帰もできる。

Slide 175

Slide 175 text

175 175 キャプチャーの仕組み • 例: 合計を仰々しく計算する fn main() { let mut sum = 0; (0..10) .map(|x| { sum += x; }) .collect::<()>(); println!("{}", sum); } ※例示のために、わざとクロージャを使っている。 普通は for ループで自分で足すか、 sum メソッドを使う。

Slide 176

Slide 176 text

176 176 キャプチャーの仕組み • 例: 合計を仰々しく計算する fn main() { let mut sum = 0; (0..10) .map(|x| { sum += x; }) .collect::<()>(); println!("{}", sum); } ※例示のために、わざとクロージャを使っている。 普通は for ループで自分で足すか、 sum メソッドを使う。 このクロージャは sum に書き込みアクセスしているので struct closure { sum: &mut i32, } *self.sum += x;

Slide 177

Slide 177 text

177 177 キャプチャーの仕組み • 例: 合計を仰々しく計算する fn main() { let mut sum = 0; (0..10) .map(|x| { sum += x; }) .collect::<()>(); println!("{}", sum); } ※例示のために、わざとクロージャを使っている。 普通は for ループで自分で足すか、 sum メソッドを使う。 このクロージャは sum に書き込みアクセスしているので struct closure { sum: &mut i32, } *self.sum += x; ここで、 self は closure または &mut closure 型 である必要がある。 ( &closure だと &i32 としてしか取り出せない)

Slide 178

Slide 178 text

178 178 変数のキャプチャーモード • 変数がクロージャ構造体にキャプチャーされるときの形態は 以下の3パターンある struct closure { var1: T1, var2: &mut T2, var3: &T3, }

Slide 179

Slide 179 text

179 179 クロージャのキャプチャーモード • 本来、キャプチャーモードは変数ごとに決められる。 • Rustでは変数ごとの指定はせず、既定または move だけ指定す る。

Slide 180

Slide 180 text

180 180 キャプチャーモードの動作 使われ方 既定 move 指定時 実装されるトレイト ムーブ (借用含む) T として キャプチャー T として キャプチャー FnOnce のみ &mut 借用 (& 借用を含む) &mut T として キャプチャー T として キャプチャー FnOnce, FnMut & 借用のみ &T として キャプチャー T として キャプチャー FnOnce, FnMut, Fn

Slide 181

Slide 181 text

181 181 ちょっと複雑なので整理 • 3者の関係、2つのインターフェースに分離できる 環境 (クロージャを 作る側) クロージャ クロージャを 呼ぶ側 作る 呼ぶ Fn系トレイト (FnOnce, FnMut, Fn) キャプチャーモード (move の有無)

Slide 182

Slide 182 text

182 182 ちょっと複雑なので整理 環境 (クロージャを作る側) 作る 既定のキャプチャー モード クロージャ • 必ずしも所有権を明け渡さな くてよい。 • クロージャ内での操作が環境 に反映される。 • 変数の所有権を取られる。 • クロージャ内での操作が環境 に反映されない。 • 環境より長く生きられない。 • 環境に干渉できる。 • 環境より長く生きられる。 • 環境に干渉しない。 move キャプチャー この事情で判断することが多い

Slide 183

Slide 183 text

183 183 ちょっと複雑なので整理 クロージャ FnOnce クロージャを呼ぶ側 • 外の変数からムーブできる。 (書き込むこともできる) • 外の変数からムーブできない。 • 外の変数に書き込める。 • 1回しか呼べない。 • 順序づければ複数回呼べる。 こちら側の事情で判断することが多い 呼ぶ FnMut Fn • 外の変数に書き込めない。 (ムーブもできない) • 順序なく複数回呼べる。

Slide 184

Slide 184 text

184 184 長生きしたい例 • flat_map 内で map したい時とか fn main() { let v = (0..5) .flat_map(|x| (0..x).map(|_| x)) .collect::>(); println!("{:?}", v); } このクロージャは長生きする必要がある

Slide 185

Slide 185 text

185 185 長生きしたい例 • flat_map 内で map したい時とか fn main() { let v = (0..5) .flat_map(|x| (0..x).map(move |_| x)) .collect::>(); println!("{:?}", v); } move をつけて長生きしてもらう

Slide 186

Slide 186 text

186 186 よくあるコンボ • クロージャが短命すぎると言われる → move をつける • すると、或る変数がムーブキャプチャーできないと言われる →仕方ないので clone する

Slide 187

Slide 187 text

187 187 明示的なキャプチャー • 構造体の特定のフィールドだけキャプチャーしたり、変数ごと にキャプチャーモードを変えたいときは、自分で切り分けてか ら move モードでキャプチャーするとよい。 let custom_closure = { let field1 = &self.field1; let bar = &bar; move || { // Use field1, bar, baz } }; ※構造体フィールドの自動分割キャプチャーはRFC2229として承認されてい るが、まだ実装されていない (2018/09)

Slide 188

Slide 188 text

188 188 クロージャと制御フロー • return/break/continue などで外側の制御フローに干渉す ることはできない。 (return と書くとクロージャから return する。) • 以下のようなRubyコードは直訳できない。 def shortcircuit_prod(a) a.inject(1) do |x, y| return 0 if x == 0 x * y end end Rubyでは関数まで脱出できる。 Rustの for はクロージャではないので、脱出が必要なら for にオプトできる。 また、 Result の FromIterator 実装などをうまく使うと、イテレーターコンビネーター だけで上手く脱出を模倣できることがある。

Slide 189

Slide 189 text

189 189 イテレータ

Slide 190

Slide 190 text

190 190 イテレータ概要 • イテレータは遅延リストのようなインターフェースを持つ。 • その要素型は、直感に反して参照型となることがある。 • collect は多様な宛先型を選べる。これを把握していると使 い勝手がかなり良くなる。

Slide 191

Slide 191 text

191 191 イテレータとは • Iterator: 遅延リスト的なインターフェース • 残りがあれば Some(x) を、打ち止めなら None を返す • ExactSizeIterator: 残りの長さがわかる • DoubleEndedIterator: 左端だけではなく右端もある。右端 から食べることもできる。

Slide 192

Slide 192 text

192 192 IntoIterator • それ自体はイテレートするのに必要な状態を持たないが、イテ レーターに変換することができるデータ。 • 例: Vec は「現在位置」の情報を持たないが、現在位置の 情報を付加することで IntoIter というイテレーターにな る。

Slide 193

Slide 193 text

193 193 要素型の怪 for i in 0..10 { // i: i32 } for x in vec![0, 1, 2].iter() { // i: &i32 }

Slide 194

Slide 194 text

194 194 要素型の怪 • Range は &i32 を返せない • 仮に返せるとすると、 collect することで「0が入った参照」「1が 入った参照」…… が同時に存在することになる。 • Range にそのための領域は存在しない! • Vec が i32 を返すことはできる • ……が、それは i32 が Clone であるため。 • 普通は要素を「見る」だけだから、要素への参照をとるのが自然

Slide 195

Slide 195 text

195 195 要素型の怪 コレクションの式 要素型 備考 start..end T T は整数 vec.iter() / &vec &T 一番よく使う vec.iter_mut() / &mut vec &mut T 各要素を編集したいとき vec.into_iter() / vec T Vec の所有権がなくなるので注意 vec.drain(..) T 指定した範囲の要素を削って取り出す vec.iter().cloned() T 複製してるだけ

Slide 196

Slide 196 text

196 196 要素型の怪 • Iterator の要素は互いに独立して存在できる (= 常に collect することができる) • 「単一の String バッファに、各行を順番に読み込んでいく」 みたいなのは抽象化できない こういう抽象化は別途 streaming_iterator クレートで提 供されている。

Slide 197

Slide 197 text

197 197 collect の魔法 • イテレータをコレクションに収集するには Iterator::collect を使う。 • collect の収集先は配列だけではなく、色々選べる。

Slide 198

Slide 198 text

198 198 collect の魔法: 基本形 • Vec に収集 let data = vec![1, 2, 3]; let result = data.into_iter().collect::>();

Slide 199

Slide 199 text

199 199 collect の魔法: ハッシュ • ハッシュマップに収集 let data = vec![(1, 4), (2, 8), (5, 7)]; let result = data.into_iter() .collect::>();

Slide 200

Slide 200 text

200 200 collect の魔法: Result • 別のコレクションの Result に収集 let data = vec![Ok(1), Err(2), Ok(3)]; let result = data.into_iter() .collect::, _>>();

Slide 201

Slide 201 text

201 201 collect の魔法: 文字列 • 文字を文字列に収集 let data = vec!['a', 'b', 'c']; let result = data.into_iter() .collect::();

Slide 202

Slide 202 text

202 202 練習問題

Slide 203

Slide 203 text

203 203 参考: Rustの練習問題集 • Rustlings: 「既存のコードスニペットを修正して、コンパイル やテストに通す」という形で練習できる。 • 今回のが難しすぎたら、こっちにするかも • Exercism.io: 練習問題を解くとメンターがレビューしてくれる サイトらしい。Rustのメンターも募集中とか。

Slide 204

Slide 204 text

204 204 今日の練習問題 • https://github.com/qnighy/rust-handson2-exercises • 4問

Slide 205

Slide 205 text

205 205 実行方法 • 以下を実行するとテストに失敗します。 $ git clone https://github.com/qnighy/rust-handson2-exercises.git $ cd rust-handson2-exercises $ cargo test

Slide 206

Slide 206 text

206 206 練習問題1 • 各行の文字を逆順にしてください。 pub fn reverse_each_line(text: &str) -> String { // … } 使うときはアンダースコアを外す

Slide 207

Slide 207 text

207 207 練習問題1 • イテレータなので map してみる pub fn reverse_each_line(text: &str) -> String { text.split_terminator("¥n").map(|line| line) }

Slide 208

Slide 208 text

208 208 練習問題1 • 文字のイテレータを取って逆順にする pub fn reverse_each_line(text: &str) -> String { text.split_terminator("¥n") .map(|line| line.chars().rev()) }

Slide 209

Slide 209 text

209 209 練習問題1 • 文字のイテレーターのイテレーターになっているので、フラッ トにする pub fn reverse_each_line(text: &str) -> String { text.split_terminator("¥n") .flat_map(|line| line.chars().rev()) }

Slide 210

Slide 210 text

210 210 練習問題1 • 各行で改行文字も足すようにする pub fn reverse_each_line(text: &str) -> String { text.split_terminator("¥n") .flat_map(|line| line.chars().rev().chain(Some('¥n'))) } std::iter::once を使うのが面倒なときに Some で代用しがち

Slide 211

Slide 211 text

211 211 練習問題1 • 文字のイテレータは文字列にそのまま collect できる pub fn reverse_each_line(text: &str) -> String { text.split_terminator("¥n") .flat_map(|line| line.chars().rev().chain(Some('¥n’))) .collect() } 戻り値型にそのまま collect するので、型は省略可能

Slide 212

Slide 212 text

212 212 別解 • 行を for 文で処理する pub fn reverse_each_line(text: &str) -> String { let mut result = String::new(); for line in text.split_terminator("¥n") { result.extend(line.chars().rev()); result.push_str("¥n"); } result }

Slide 213

Slide 213 text

213 213 関数シグネチャの設計

Slide 214

Slide 214 text

214 214 関数シグネチャの設計: 概要だけ • 参照で受け取り、所有権で返すのが基本 fn(&str) -> String • 単純コピーが可能ならコピー渡しする fn(i32) -> String • 受け取った参照の一部を返すときは参照返しになる fn(&str) -> &str • 書き換えられるものは &mut 参照で受ける fn(&mut Context) -> () • 所有権が絶対必要なときや、ファイナライズ系はムーブ渡し fn(Digest) -> [u8; 16]

Slide 215

Slide 215 text

215 215 型とトレイト

Slide 216

Slide 216 text

216 216 型とトレイト: 概要だけ • Rustでは型には十分な抽象化能力を与えられていない。 • 型が値の表現をあらわし、トレイトが型の性質を表す。 • 「型そのものの性質」であるようなトレイトとしては、 Eq や Default などがある。 • 間接的に「値の性質」とみなせるトレイトもある。 Fn や Iterator, Future, Debug, Error など。 • そのようなトレイトは「トレイト安全性を満たす」といい、 dyn Trait という型とみなすことができる。

Slide 217

Slide 217 text

217 217 型とトレイト: 概要だけ • トレイトを型とみなす dyn Trait は実行時コストが嵩むため 好まれない。 • また、 Send / Sync やライフタイムに関する情報が失われるというデ メリットもある。 • 逆に、それ以外のケースではトレイトは全てコンパイル時に解 決されるため、ほとんどオーバヘッドがない。 (impl Trait を含む) • トレイトは抽象化の数しか存在しないが、型はたくさん作って よい風潮がある。 Fn や Iterator はそのような「専用型」の パターンを踏襲しており、HaskellよりC++に近い。

Slide 218

Slide 218 text

218 218 データ構造の設計

Slide 219

Slide 219 text

219 219 データ構造の設計: 概要だけ • Rustでデータ構造を設計するときの最も特異な性質は「循環参 照の扱い」にある。 • 実装レベルでいうと、GCを持たず、RAIIを許可しているため、 循環参照との相性が悪い。 • ユーザーレベルのgcライブラリはあるが、おそらく使い勝手は良くな い • 循環参照は、支配関係が明確でない設計のサイン……かもしれ ない

Slide 220

Slide 220 text

220 220 データ構造の設計: 概要だけ • 他言語のパターンにあわせてArc/Weakで設計したくなるが、 おそらくこれはイディオマティックではない。 • 支配関係が明確なら、それに沿った参照だけを残して、反対向 きのリンクは文脈として持ち回すという方法がある。 • そうではない場合、より大きな親玉のもとで平等に管理するの が正しいかもしれない。 • しばしば、 typed_arena クレートと組み合わせられる。