class: title # Rustはなにがすごいのか ## メモリセーフ編 ### hatsusato 2018/5/21 --- # 自己紹介  ## @hatsusato - KMC7回生 - .oneline[ 京都大学大学院情報学研究科通信情報システム専攻 五十嵐・末永研究室 D1 ] - 研究テーマ: C言語プログラムの静的解析手法 - C言語撲滅委員会会員 - 作画オタク --- # アウトライン Rustの言語仕様の背後にある気持ちの理解を深める - 前編: Rustのメモリ安全性保証の仕組みについて - 後編: Rustのスレッド安全性保証の仕組みについて 対象聴衆: - Rustを学んでみたが難しいと思った人 - このスライドでRustが書けるようにはならない ??? - Rustは誰にとっても難しいので、Rustを学ぼうとした人全員が対象。 - Rust?なにそれ?な人はそんな言語があるのかふーんぐらいでよろしく。 --- # Rustとは プログラミング言語 Rust (https://www.rust-lang.org/ja-JP/) .center[
] ??? - デモを実行してみたりして少し遊ぶ。 --- # Rustとは .blockquote[.oneline[ Rustは速度、安全性、並行性の3つのゴールにフォーカスした システムプログラミング言語です。 ]] ## 特徴 .double-column[ - ゼロコスト抽象化 - ムーブセマンティクス - 保証されたメモリ安全性 - データ競合のないスレッド - トレイトによるジェネリクス - パターンマッチング - 型推論 - 最小限のランタイム - 効率的なCバインディング ] ??? - 先ほどのページから抜粋。 --- # Rustとは .blockquote[.oneline[ Rustは速度、*安全性*、並行性の3つのゴールにフォーカスした システムプログラミング言語です。 ]] ## 特徴 .double-column[ - ゼロコスト抽象化 - ムーブセマンティクス - *保証されたメモリ安全性* - データ競合のないスレッド - トレイトによるジェネリクス - パターンマッチング - 型推論 - 最小限のランタイム - 効率的なCバインディング ] ??? - これらのうち、前編では保証されたメモリ安全性に注目する。 --- # 保証されたメモリ安全性 - *保証された*とは? - コンパイル時に静的解析を行い、プログラムの安全性を検証 - *メモリ安全性*とは? - 不正なメモリアクセスによるバグや脆弱性がない状態 - *不正なメモリアクセス*とは? - バッファオーバーフロー - ダングリングポインタ - etc. --- class: question # メモリ安全性がなぜ必要? ## ガーベジコレクションがないから - 他の言語のほとんどにはガーベジコレクションがある - ガーベジコレクションがある言語の多くはメモリ安全 Rustはガーベジコレクションのない言語だがメモリ安全性を保証 ??? - 正確には、ガーベジコレクションだけではバッファオーバーフローは防げない。 ガーベジコレクションに加えてバッファオーバーフロー対策がある言語がメモリ安全な言語。 - 以下では簡単のためにメモリ安全性を時間的メモリ安全性の意味で用いる。 --- # どうやってメモリ安全に? Rustの三大新概念 1. Ownership 2. Borrowing 3. Lifetime ??? - この三要素が前編で説明したいこと。 --- # 1. Ownership - プログラム中の値とその値を所有する変数との対応 - 所有する変数をOwnerと呼ぶ - 値は常にただ一つのOwnerと対応 - Ownerがスコープを抜けると、対応する値を破棄 ```rust let x = Box::new(0); { let y = x; } // boxed 0 is dropped ``` ??? 1. `Box`型の変数`x`が値`0`を所有しOwnerとなる。 2. `y`に`x`を代入すると値`0`の所有権が移動し`y`がOwnerとなる。 3. `y`がスコープを抜け、`y`が所有する値`0`が破棄される。 --- class: question # それってC++のunique_ptrでは? ## はい しかし、Rustは安全性の*保証*をする点で異なる ```rust let x = Box::new(0); { let y = x; } println!("{}", x); // compile error ``` ??? - `y`への代入によって`x`はもはや有効な値を指していないので、 コンパイルエラーにより不正なアクセスを防ぐ。 --- class: question # それってC++のunique_ptrでは? ## はい 一方、C++は運用でカバー ```c++ auto x = std::make_unique<int>(0); { auto y = std::move(x); } std::cout << *x << std::endl; // pass but undefined ``` ??? - このコードはコンパイルは通るが、実行時に未定義動作となり、おそらくセグフォなどを引き起こす。 - C++に限らず、これまでのほとんどの言語はこの種の問題を、 注意深く実装することによって回避する。 --- # 2. Borrowing 参照をつくること ```rust let mut x = Box::new(0); let y = &mut x; ``` .oneline[ Borrowingにまつわる不正な操作を、 AliasingおよびMutabilityの制約により防ぐ ] ??? 1. 変数`x`が値`0`を所有する。 2. 変数`y`が`x`へのmutable参照をもつ。 --- # Aliasing - 1つの値に対する
変更不可能な
immutable
参照はいくつ存在してもよい ```rust let v = vec![0, 1, 2]; let x = &v[0]; let y = &v[1]; let z = &v[2]; println!("{} {} {}", &x, &y, &z); ``` ??? - `x`, `y`, `z`は同じ値`v`をimmutableに参照する。 --- # Mutability - 1つの値に対する
変更可能な
mutable
参照は高々一つである - .oneline[
変更可能な
mutable
参照が存在するときには、 その値を指す参照が同時に複数あってはならない ] ```rust let mut x = Box::new(0); { let y = &mut x; } let z = &mut x; ``` ??? - `y`, `z`はいずれも同じ値`x`を参照するが、 スコープの分割によって同時には参照していない。 --- class: question # Borrowingの検査は何のため? ## 無効になったエイリアスへのアクセスを防ぐため - .oneline[ オブジェクトを指す参照があるときにそのオブジェクトを 変更すると、参照が無効になる可能性がある ] - .oneline[ 1つのmutable参照のみがオブジェクトを参照しているならば、 無効なエイリアスへのアクセスは発生しない ] ```rust let mut v = vec![0]; let r = &v[0]; v.push(0); // won't compile println!("{}", r); // UB ``` ??? - もしこのプログラムがコンパイルできた場合、 `v.push()`の呼び出しの結果、`r`の参照が無効になる可能性がある。 - `println!`での`r`へのアクセスによって未定義動作となりうる。 - `v.push()`の呼び出しにおいて、`self`として`&mut v`が渡される。 このとき、`r`と`&mut v`の2つの参照が発生する。 - Rustはこれを認めないことで無効なエイリアスの発生を防ぐ。 --- # 3. Lifetime .oneline[ OwnershipやBorrowingの検査に必要な情報は Lifetime parameterに含まれる ] ```rust struct Foo<`'a`> { x: &`'a` i64, } fn choose<`'a,'b`>(x:&`'a` i64, y:&`'b` i64) -> &`'a` i64 { x } ``` ??? - ハイライトしている部分がLifetime parameter。 --- class: question # Lifetime parameterがなぜ必要? ## 関数呼び出しを含むプログラムの解析のため 関数呼び出しがあると情報の伝搬ができない - .oneline[ Lifetime parameterを用いて、 関数呼び出し前後のOwnership, Borrowing関係を明示 ] - これにより、*関数定義ごとの解析*が可能に ```rust let y = 0i64; let x: &i64; { let z = 1i64; x = choose(&y, &z); } ``` ??? - `choose`が`&y`, `&z`のどちらを返すのかがわからないと、`x`の寿命を決定できない。 - Lifetime parameterのおかげで`choose`の定義を見なくても、 宣言のみから戻り値の寿命を決定できる。 --- class: question # Lifetime parameterはどこから来た? ## 先行研究がある RustはCycloneという先行研究を参照 - Lifetime parameterはCycloneではregion annotationと呼ばれる - CycloneのLifetime管理の仕組みをそっくりそのまま採用 ??? - 僕は自分の研究の過程でCycloneの論文を読んでいるので詳しい。 - region annotationはregion variableやregion qualifierとも呼ばれる。 --- # まとめ Rustはコンパイル時に静的解析を行ってメモリ安全性を保証 - Ownership: メモリ安全のため - Borrowing: 無効なエイリアスを防ぐため - Lifetime: 関数呼び出しのため ??? - 個人的に思うRustの一番すごいところは、 (flow-insensitiveな)コンパイル時静的解析を備えた(実用的)プログラミング言語を 初めてちゃんと実装したところにある。 --- class: title # Rustはなにがすごいのか ## スレッドセーフ編 ### hatsusato 2018/5/28 --- # Rustとは .blockquote[.oneline[ Rustは速度、安全性、*並行性*の3つのゴールにフォーカスした システムプログラミング言語です。 ]] ## 特徴 .double-column[ - ゼロコスト抽象化 - ムーブセマンティクス - 保証されたメモリ安全性 - *データ競合のないスレッド* - トレイトによるジェネリクス - パターンマッチング - 型推論 - 最小限のランタイム - 効率的なCバインディング ] ??? - 後半では並行性やデータ競合のないスレッドについて述べる。 --- # データ競合 .float-left[ - .oneline[ 同一のメモリ領域に対して 非同期に複数のアクセスがあり、 それらの中に書き込み操作が 含まれること ] - .oneline[ 詳しくは、 [C++ マルチスレッド 入門](https://www.slideshare.net/KMC_JP/c-47378581) → を参照 ] ] .float-right[
] ??? - データ競合はそれだけで大きなトピックなのでここでは深入りしない。 - KMCの先輩の昔の発表資料がわかりやすいので、そちらを参照推奨。 --- # 方針 Rustでは、スレッド安全性を*型の制約*によって保証 - 型にスレッドをまたいでよいかどうかを示す印をつける - `Send` traitを実装する型は別スレッドへ転送できる - `Sync` traitを実装する型は別スレッドと共有できる - .oneline[ スレッド間のデータの受け渡しに関わる関数の 引数の型制約により危険な操作を制限 ] - `std::thread::spawn`は引数として`Send`を実装する型を受け取る ??? - ここではデータ競合がないことを簡単のためにスレッド安全であると呼ぶことにする。 - Rustはデータ競合を防ぐが、デッドロックなどのその他の問題についての保証はない。 --- # トレイト 型がもつべきメソッド(仕様)を記述する仕組み - トレイトの例 ```rust trait Speak { fn speak(&self); } struct Person { name: String } impl Speak for Person { fn speak(&self) { println!("my name is {}", &self.name) } } fn say_something(s: &Speak) { s.speak() } ``` ??? - `speak`ソッドをもつという仕様を表す`Speak`トレイトと、 `Speak`トレイトを実装する`Person`構造体の例 --- # 特殊なトレイト `Send` / `Sync` は以下の特徴をもつ特殊なトレイト .less-margin[ - marker trait - メソッド仕様をもたず印として働く - auto trait - .oneline[ すべてのメンバがそのトレイトを実装するとき、 自動的に実装される ] - unsafe trait - 安全性のために*何らかの制約*を満たす必要がある - その制約を満たしているかどうかは*トレイトの実装者が保証*する ] ??? ```rust pub unsafe auto trait Send { } #[lang = "sync"] pub unsafe auto trait Sync { } ``` --- # Sendトレイト 値渡しにより別のスレッドに渡しても安全なことを示す - `std::thread::spawn` の引数が満たすべき仕様 例: - スカラー型 (`i64`, `f64`, `bool`, etc.) - `Sync` な型のimmutable参照 (`&T`) - メンバの型が全部 `Send` なstruct (auto trait) ??? - `T: Sync`な型のimmutable参照`&T`の安全性は後述の`Sync`の定義よりわかる。 - 値渡しができる型は大体`Send`だが、 `Rc`などのポインタを内部にもつ型は`!Send`。 --- # Syncトレイト immutable参照(`&T`)を別のスレッドに渡しても安全なことを示す 例: - スカラー型 (`i64`, `f64`, `bool`, etc.) - `Sync` な型の参照 (`&T`, `&mut T`) - `Mutex<T>` (`T: Send`) - `std::sync::atomic` のアトミック型 (`AtomicBool`, `AtomicPtr`, etc.) - メンバの型が全部 `Sync` なstruct (auto trait) ??? - immutable参照を渡すということは、 mutable参照が1つもないということが、borrow checkerにより保証される。 したがって、interior-mutabilityがなければ渡しても安全である。 - これにより、スカラー型や参照の参照は`Sync`である - mutable参照のimmutable参照について、外側のimmutablityが優先する。 - interior-mutabilityをもつ型であっても`Send`な型であれば、 `Mutex`によってスレッド安全に扱うことができる。 - アトミックな型は当然スレッド安全。 - `Sync`な型のコンテナの多くも`Sync`を満たす(例: `Box<T>`, `Vec<T>`) --- # まとめ - スレッド安全性は型制約によって実現される - 型のスレッド安全性は実装者が責任をもつ(unsafe) - .oneline[ 言語機能自体はスレッド安全性を保証しないが、 型の組み合わせ方の安全性は確認する ]