Slide 1

Slide 1 text

ジェネリクスと 静的ディスパッチと エラーメッセージ 2016/11/21 RUST の LT 会!

Slide 2

Slide 2 text

ABOUT ME agatan Twitter: GitHub: 趣味は言語処理系/ コンパイラいじり 自作言語の妄想をするのがすきです システムプログラミング系の研究室にいます @agatan_ http://github.com/agatan/

Slide 3

Slide 3 text

RUST のジェネリクス 一つのコードで複数の型に対応する / trait 制約を付けられる 使うときは具体的な型をいれる / そして型推論 fn hello(x: T) -> String { format!("Hello {}", x) }

Slide 4

Slide 4 text

どうコンパイルされるか Rust のジェネリクスは monomorphization によってコ ンパイルされる インスタンス化されるたびに専用の関数をつくる fn snd(x: (T, U)) -> U { x.1 } let a: (i64, i64) = (0, 1); snd(a); // => 1 let b: (Vec, Option) = (vec![], None); snd(b); // => None この例では と がインスタンス化されている let a: (i64, i64) = (0, 1); snd_i64_i64(a); // <= 専用 関数 呼 let b: (Vec, Option) = (vec![], None); snd_vec_i32_option_u64(b); // <= 専用 関数 呼 こんな感じにコンパイルされる

Slide 5

Slide 5 text

ジェネリクスとトレイト trait Greet { fn greet(&self) -> String; } impl Greet for String { fn greet(&self) -> String { format!("Hello, I'm {}", self) } } impl Greet for i64 { fn greet(&self) -> String { format!("Hello, I'm No.{}", self) } } ジェネリクスにトレイトによる制約がつけられる fn conversation(t: T, u: U) { println!("T => {}", t.greet()); println!("U => {}", u.greet()); } monomorphization によってコンパイルされる の部分はどうなる?

Slide 6

Slide 6 text

静的ディスパッチ 専用の関数はどうコン パイルされるか コンパイル時にどの関数を呼ぶかすべてわかっている => 静的ディスパッチ の っぽい fn conversation_i64_String(t: i64, u: String) { println!("T => {}", t.greet()); // => greet i64::greet println!("U => {}", u.greet()); // => greet String::greet }

Slide 7

Slide 7 text

静的ディスパッチ 利点 インライン展開できる!! 関数を動的に探す必要がない!!( ただの関数呼び 出し) 欠点 バイナリが大きくなる... 分割コンパイルできない

Slide 8

Slide 8 text

動的ディスパッチ どの関数を呼ぶか実行時に決定する の仮想関数 class A { public virtual void greet() { std::cout << "Hello, I'm A\n"; } } class B: public A { public virtual void greet() override { std::cout << "Hello, I'm B\n" } void greet(A const& a) { a.greet(); // A::greet? B::greet? } 典型的には仮想関数のテーブルをオブジェクトに紐付 けておく 実行時に対象オブジェクトの に対応する関数 ポインタを探す

Slide 9

Slide 9 text

動的ディスパッチ Rust ではトレイトオブジェクトを使う / = 「 を満たす何か」のベ クタ とはかけない 利点 バイナリサイズの節約 分割コンパイルを諦める必要がない 欠点 実行時コストがかかる

Slide 10

Slide 10 text

型が爆発する monomorphization を進めていくと,型が爆発する 自前のちょっとしたパーサコンビネータの例 let mut parser = char('"') .and(take_while(|x: &char| *x != '"')) .and(char('"')) .map(|((_, s), _)| s); の型は Map< And< And< Char>, TakeWhile<[closure@src/main.rs:253:25: 253:45], StrInput<'a>, String> >, Char> >, [closure@src/main.rs:255:14: 255:29] >

Slide 11

Slide 11 text

EXPRESSION TEMPLATE 式を型で表す! c.f. Boost.Spirit (C++ のパーサコンビネータ) Rust には型推論があるので,型の複雑さはほとんど気 にならない! 関数定義のときは返り値の型を明示しなくてはならな くて若干つらい... この辺は読みやすさとのトレードオフ Rust のコンパイルエラーはわかりやすいので expression template も(そこまで)辛くない!!

Slide 12

Slide 12 text

わざとエラーを起こしてみる としてコンパイルエラーを起こ してみると => 読みやすい!! 良い感じに詳細が省かれた型名を表示してくれる どういう基準でやっているのかはよくわからない...

Slide 13

Slide 13 text

もっとエラーを起こしてみる とやってみる までは合っている.3 つ目の が間違っている. => 本当に読みやすい!!!!

Slide 14

Slide 14 text

まとめ Rust のジェネリクスは静的に色々解決する 動的にもできる ( トレイトオブジェクト ) ジェネリクスしまくると型が爆発する!! Rust の型推論は強力なので,ほとんど気にならない 型が合わなくなってもエラーメッセージが本当にわか りやすい!!! expression template 的なことをしてもそんなに辛く ない 型以外にもエラーメッセージは本当に読みやすくて 最高