--- lang: ja-jp tags: Rust title: All things to know about Rust --- # All things to know about Rust [The Rust Programming Language](https://doc.rust-jp.rs/book/second-edition/foreword.html)から学んだことのメモ。このテキストの11章までマスターすれば、基本的な処理は書けるようになると思われる。 ## Useful links - [RustでPythonを高速化する](https://hackmd.io/@moriaki3193/B1j13upcr) ## Memo - 所有権はメモリのヒープ領域に格納されるオブジェクトの値に関わる概念 - 参照はそのような値を束縛する変数についての概念で、所有権を持たない - 関数に参照を渡すことを借用と言う - スライスは参照と同じように、所有権を持たないオブジェクトである - `char`, `String`, `&str` ## Spec ### Jargons - `String::new()`のような関数は、**関連関数**と呼ばれる。他の言語で言えば、スタティックメソッドと同じ。 ### 複合型(タプルやリスト)のImmutabilityについて > 束縛される複合型の要素についてもImmutableであることが保証される。複合型で一つの型ということ。 以下のように変数`a`に複合型の一つとして配列を束縛し、0番目の要素を変更しようとするとコンパイルエラーになる。 ```rust= fn main() { let a = [1, 2, 3]; a[0] = 1; println!("{}", a); // ERROR: cannot assign to `a[_]`, as `a` is not declared as mutable } ``` `a`はImmutableなものとして宣言されており、複合型が内包する値についての変更が許されない。正しくは`let mut a = [1, 2, 3];`のように、Mutableなものとして宣言する。 ### 変数のシャドーイング 同じシンボルの変数を複数回定義することができる。 ```rust= // まったく実用的ではないが、こんな感じで書けるという例。 let a = 1; let mut a = "Hello"; // 型をstrにしたい! mutはなくても良い println!("{}, world!", a); ``` ### 文字型と文字列 Rustで文字を扱う場合には、基本的に`char`を扱うことになる。charはシングルクオート`'`で囲まれた文字一つがリテラルとなる。では文字**列**は?文字列は文字の列であるから、`char`のリスト(のようなもの)としてRustでは扱われることになる。とすれば、目地的に対応する型はない?その詳細な解答は[ここ](https://doc.rust-jp.rs/book/second-edition/ch08-02-strings.html)で紹介されている。 ### タプルの要素の呼び出し 複数の型を持つ値を一つにまとめあげて管理可能なタプル。その要素へのアクセスには、`.`を利用する。次のようにすれば良い。 ```rust= let tup = (42, 3.14, "Hello, world!"); println!("pi is approximately {}", tup.1); // 他の言語(Pythonなど)を利用している人からすると不思議な書き方ではあるが... ``` ### 関数アラカルト #### 関数の定義箇所 Rustは関数の定義されている順番については気にしない。関数が定義されていることのみを気にする。つまり、次のような順番で定義された関数であっても、コンパイルエラーにはならない。 ```rust= fn main() { // 定義は呼び出し箇所の後ろにあるが、きちんと動作する。 another_function(); } fn another_function() { println!("Hello, world!"); } ``` #### 関数の構造 > 関数本体は、文が並び、最後に式を置くか文を置くという形で形成されます。 Rustは式指向言語であるため、文と式には大きな違いが存在する。式は値を返す。また、式は終端にセミコロン`;`を含まない。 次のような関数の定義がコンパイルエラーを引き起こす原因について考えてみよう。 ```rust= fn plus(x: i32, y: i32) -> i32 { x + y; } ``` ### スレッド #### 生成 `thread::spawn()`を利用する。 #### スレッド間で参照される変数を扱う > ミューテックスは並行処理を制御するための機構です: その内容へ同時アクセスできるのは1スレッドに限定されます。 `use std::sync::Mutex;`の宣言を忘れずに。 MutexとはMUTual EXclusionの省略であり、日本語では排他制御と呼ばれる。この構造体の初期化で代入される変数に対して、排他ロックが施される。 ### Stack vs Heap Rustでは所有権の概念を扱うにあたり、メモリ空間内のスタック領域とヒープ領域を気にする必要があるため、簡単にまとめる。 #### Stack - LIFO(Last In First Out)で値が格納される - そのため、実行時では定数時間で値へのアクセスが可能になる - 取り出すべき値は常にスタックの一番上に配置されている - ヒープよりも高速に動作する - コンパイル時に格納する値のサイズがわかっていなければならない - 操作はPush(格納)とPop(取り出し) #### Heap - サイズが可変であるデータについて格納する領域 - ヒープ領域内の値への**ポインタ**をスタックに格納して呼び出す形式で利用される > コンパイル時にサイズがわからなかったり、サイズが可変のデータについては、代わりにヒープに格納することができます。(中略)ヒープにデータを置く時、あるサイズのスペースを求めます。 OSはヒープ上に十分な大きさの空の領域を見つけ、使用中にし、ポインタを返してきます。 なぜ所有権に関わる概念として紹介されているかについては、以下の引用を参照。 > どの部分のコードがどのヒープ上のデータを使用しているか把握すること、ヒープ上の重複するデータを最小化すること、 メモリ不足にならないようにヒープ上の未使用のデータを掃除することは全て、所有権が解決する問題です。 一度所有権を理解したら、あまり頻繁にスタックとヒープに関して考える必要はなくなるでしょうが、 ヒープデータを管理することが所有権の存在する理由だと知っていると、所有権がありのままで動作する理由を 説明するのに役立つこともあります。 ### 所有権 :::info - Rustの各値は、所有者と呼ばれる変数と対応している。 - いかなる時も所有者は一つである。 - 所有者がスコープから外れたら、値は破棄される。 ::: - 基本型、複合型は全てコンパイル時にスタック領域に格納される。 - shallow copyではなく、moveが行われる。 #### いつ「所有権」を考えるのか ==ヒープ領域上に展開される値を利用した代入などの操作を行う時に「所有権」を考える必要が出てくる。== ```rust= // このコードはエラーにならない: // リテラルを格納する変数xを変数yにコピーするため、コンパイル時にスタックされる let x = 5; let y = x; ``` > このコードは一見、今学んだことと矛盾しているように見えます: cloneメソッドの呼び出しがないのに、xは有効で、yにムーブされませんでした。 > その理由は、整数のようなコンパイル時に既知のサイズを持つ型は、スタック上にすっぽり保持されるので、 実際の値をコピーするのも高速だからです。これは、変数yを生成した後にもxを無効化したくなる理由がないことを意味します。 `String`を利用する場合には、少し考えることが増える。 ```rust= let s1 = String::from("hello"); let s2 = s1 ``` **値には所有権が1つ設定されている。値が束縛される変数は、所有権の仕組みにより必ず1つだけになる** ##### 1. s1が定義され、ヒープ上に値が展開される ![image](https://doc.rust-jp.rs/book/second-edition/img/trpl04-01.svg) ##### 2. s1の定義がs2にコピーされる i.e. メモリのヒープ領域上でのポインタや長さ、キャパシティの情報がコピーされる。 ![image](https://doc.rust-jp.rs/book/second-edition/img/trpl04-02.svg) ##### 3. (Rustでは違うが)以下のような変数と値の束縛関係になる Rust上での挙動とは異なることに注意すること。 ![image](https://doc.rust-jp.rs/book/second-edition/img/trpl04-03.svg) ##### 4. s1が保持していた値に対する所有権がs2に移動し、s1が向こうになる > 他の言語を触っている間に"shallow copy"と"deep copy"という用語を耳にしたことがあるなら、 データのコピーなしにポインタと長さ、許容量をコピーするという概念は、shallow copyのように思えるかもしれません。 ですが、**コンパイラは最初の変数をも無効化する**ので、shallow copyと呼ばれる代わりに、 ムーブとして知られているわけです。この例では、s1はs2にムーブされたと表現するでしょう。 ![image](https://doc.rust-jp.rs/book/second-edition/img/trpl04-04.svg) Rustでは、自動的なデータの`deep copy`が行われることが**絶対に**ない。Rust上で行われる「自動」コピーはその点で実行性能を低下させるものではないことが保証される。 #### 所有権「だけ」の世界の問題点 - 関数の引数として呼び出し元のブロックから所有権をmoveした変数を、関数実行後に再利用したい場合には、常に関数の返り値としてもともとの値を返す必要が出てくる この問題の解決策が「参照」となる。 ### Struct #### Methods メソッドは構造体(struct)について定義される関数となる。`impl`キーワードを利用して、その構造体に紐づくメソッドを定義できる。 ```rust= struct Rectangle { width: u32, height: u32, } impl Rectable { fn area(&self) -> u32 { self.width * self.height } } ``` #### Related Functions `impl`を利用してメソッドを定義すること以外に、構造体に関連関数を定義することも可能であり、`self`を引数に取らない関数を定義することで達成できる。その際の呼び出し方は`strct.method()`ではなく、`strct::related_function()`のようになることに注意する。 ```rust= impl Rectangle { fn square(size: u32) -> Rectangle { Rectangle{ width: size, height: size } } } ``` > `::`という記法は、関連関数とモジュールによって作り出される名前空間両方にしようされます。 ### Enum(列挙型) > 列挙型は、取りうる値をすべて列挙でき、 これが列挙型の名前の由来です。 `Option<T>`は`Some(T)`と`None`を列挙子に取る列挙型。 ```rust= fn main() { let hello = "hello"; let world: Option<&str> = Some("world"); let msg = match world { Some(s) => hello + ", " + s, None => hello + ", " + "moriaki", }; println!("{}", msg); } ``` `match`式で全ての可能性を列挙したくない時には、`_`プレースホルダーを利用して記述を省略できる。 ```rust= let some_u8_value = 0u8; match some_u8_value { 1 => println!("one"), 3 => println!("three"), 5 => println!("five"), 7 => println!("seven"), _ => (), }; ``` #### if let > `if let`記法で`if`と`let`をより冗長性の少ない方法で組み合わせ、残りを無視しつつ、一つのパターンにマッチする値を扱うことができます。`Option<u8>`にマッチするけれど、値が3の時にだけコードを実行したいプログラムを考えてください。 ```rust= let some_u8_value = Some(0u8); match some_u8_value { Some(3) => println!("three"), _ => (), }; // という上のコードは、次のようなシンタックスシュガーで置き換えることができる // `==`のように評価しないのは、列挙子が値を保持する場合に、プレースホルダーに「代入」を行うため // だと考えられる...? if let Some(3) = some_u8_value { println!("three"); } // こんな感じの書き方も可能: let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {:?}", state); } else { count += 1; } ``` > これで、enumを使用してワンセットの列挙された値のどれかになりうる独自の型を生成する方法を講義しました。標準ライブラリの`Option<T>`が型システムを使用して、エラーを回避する際に役立つ方法についても示しました。enumの値がデータを内部に含む場合、処理すべきケースの数に応じて、`match`から`if let`を使用して値を取り出し、使用できます。 ### モジュールを利用したコードの体系化 - `mod`キーワードで新規モジュールを宣言します。モジュール内のコードは、この宣言の直後の波括弧内か、別のファイルに存在します。 - 標準では、関数、型、定数、モジュールは非公開です。`pub`キーワードで要素は公開され、名前空間の外からも見えるようになります。 - `use`キーワードでモジュールやモジュール内の定義をスコープに入れることができるので、参照するのが楽になります。 ### ライフタイム コンパイラの気持ちになって次のコードを読めば、ライフタイムを明示しなければならない理由に気づくことができるはず。コンパイルエラーになる理由について考えてみよう。 ```rust= fn main() { // 長い文字列は長い let string1 = String::from("long string is long"); { let string2 = String::from("xyz"); let result = longest(string1.as_str(), string2.as_str()); } // ここでstring2は破棄されるよね? println!("The longest string is {}", result); } ``` ## Case Study ### 標準入力から入力を受け取る ```rust= use std::io; fn main() { io::stdin().read_line() // Result型を返す: OkにはStringが入る } ``` ### 数値型、何使う? > では、どの整数型を使うべきかはどう把握すればいいのでしょうか?もし確信が持てないのならば、 Rustの基準型は一般的にいい選択肢になります。整数型の基準はi32型です: 64ビットシステム上でも、 この型が普通最速になります。isizeとusizeを使う主な状況は、何らかのコレクションにアクセスすることです。 ちなみに浮動小数点数の場合には、`f64`が基本型になる。現代のCPU事情では、f32でもf64でも処理速度はほぼ同じであり、かつ倍精度の方が多くの情報を保持できるので、よほどの理由がない限り、f64を利用すれば良い。 ### 配列とベクタ、どちらを使おうか... > 配列は、ヒープよりもスタック(スタックとヒープについては第4章で詳つまびらかに議論します)にデータのメモリを確保したい時、 または、常に固定長の要素があることを確認したい時に有効です。 ただ、配列は、ベクタ型ほど柔軟ではありません。ベクタは、標準ライブラリによって提供されている配列と似たようなコレクション型で、 こちらは、サイズを伸縮させることができます。配列とベクタ型、どちらを使うべきか確信が持てない時は、 おそらくベクタ型を使うべきです。第8章でベクタについて詳細に議論します。 ### String vs &str > 名前には &str 型ではなく String 型を選びました。一般的に、データを所有する型を用いた方が、 データを参照する型の利用よりも簡単になります。 #### 文字列の更新 String値を連結する`+`演算子や、format!マクロが存在する。 ```rust= let mut s = String::from("foo"); // &str(文字列スライス)からStringへ s.push_str("bar"); // &str(文字列スライス)をStringに連結する let mut t = String::from("lo"); s.push('l'); // charをStringに連結する ``` Rustの文字列操作は奥が深い。 ### HashMap `std::collections::HashMap`に存在する。 ```rust= use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); ``` > i32のようなCopyトレイトを実装する型について、値はハッシュマップにコピーされます。 Stringのような所有権のある値なら、値はムーブされ、リスト8-22でデモされているように、 ハッシュマップはそれらの値の所有者になるでしょう。 ## Skunkwork - 差分プライバシーを実現するSVMをRustで実装し、利用に際してPythonで呼び出すことができるようにする。 - RankingSVM, LambdaMART, Baysian Personalized Rankingなど、ランキング学習のモデルをRustで実装し、PythonとGolangに対するAPIを提供する