Rust

The Rust Programming Language 日本語版 を 自分で学習したときのメモ

4. Ownership を理解する


Ownership のおかげで Rust はガベージコレクタ無で安全性を担保できる。
  1. Ownership (所有権) とは何か
  2. Rust ではメモリは、コンパイラがコンパイル時にチェックする一定の規則とともに、所有権システムで管理される。

    Ownership Rules (所有権規則)

    Variable Scope (変数のスコープ)

    ownwership の最初の例として 変数の scope について説明する。 scope は、プログラムの中であるアイテムが valid な範囲のことである。
    Listing 4-1: 変数と、それがvalidなscope
    {                      // s はvalid(有効)ではない
        let s = "hello";  // s は valid
        // s を使う       // s は valid
    }                     // scope を抜けたので sはもはや valid ではない。
    o
    

    String 型

    既に3章で紹介したデータ型はどれも既知のサイズであり、スタックの上に保存され、 scopeを抜けると捨てられるものであった。 ownership を理解するにはもう少し複雑なデータ型が必要になる。 異なるスコープで同じ値を使う必要がある場合は、新しい独立したインスタンスがコピーによって生成される。

    しかしここでは、ヒープに保存されるデータを扱って、Rust がいかにしてその値が消去される時を知るかを見ていく。 String型がよい例となることだろう。

    <

    今まで見てきた String literal は immutable (変更付加) である。

    ユーザ入力を保存するのには String 型がある。 String は mutable (変更可)できる。 String 型は heap にデータを割り当てる。 String 型のtextの量はコンパイル時には不明である。

    String型変数の例
    let mut s = String::from("hello");
    
    s.push_str(", world"); // push_str() はStringにリテラルを追加する
    println!("{s}""); // this will begin
    

    Memory and Allocation (メモリと割り当て)

    String literal の場合は、コンパイル時にサイズが確定しているのでテクストが最終の実行可能ファイルに 直接ハードコーディングされる。これがString literal が高速で効率的な理由である。

    String型の場合は、mutable (可変)で増加可能なテキストをサポートするために、 コンパイル時には未知の量のメモリをヒープに確保する必要がある。すなわち

    最初の項目は String::from を呼び出せば実現できる。

    2番目の項目は異なる。ガベージコレクタを備えた言語では、使われなくなったメモリはガベージコレクタが処理するので、 プログラマが考慮する必要はない。 ガベージコレクタを持たない言語では、使われなくなったメモリを開放するのはプログラマの責任となる。 これを適切に行うことはプログラマにとって大変難しい仕事である。

    Rust では異なる道を進んでいる。メモリを所有している変数がそのスコープを抜けたらメモリは自動的に返還される (drop関数による)。 スコープの例である Listing 4-1 を、 String literal の代わりに Srring を使って記述した版は次のようになる。

    String型変数とスコープの例
    {
        let s = String::from("hello"); // s is valid from th e point forward
    
        // s を使う                  // s is valid
    }                                // s is no longer valid
    

    ヒープ上に確保されたデータを複数の変数が参照していると複雑な場面が起こり得る。 そのような場面について以下で検討していく。

    Variables and Data Interacting with Move (moveによる変数とデータの相互作用)

    Listing 4-2: 変数x,yに整数値を代入する
        let x = 5;
        let y = x;
    

    整数は固定のサイズを持つ単純な値なので、2つの'5'という整数値はスタック上に確保されている。

    Listing 4-2b: 変数s1, s2 に文字列を代入する
        let s1 = String::from("hello");
        let s2 = s1;
    

    変数 s1, s2 の値は5個の文字からなる文字列であり、これはヒープ上に取られて共有されている。 変数 s1, s2 が scope を抜けたらそれぞれがデータを drop すると、2重開放エラーとなってしまう。

    メモリ安全性を確保するために、let s2=s1; 行の後は s1 はもはや valid (有効)ではないと考える。 そのため s1 のscope外に移動したときには何も開放されない。 s2 だけが有効なので、スコープを抜けたら s2 だけがメモリを解放する。

    Listing 4-2c:所有権が移動するプログラム
    fn main() {
        let s1 = String::from("hello");
        let s2 = s1;
    
        println!("{s1}, world");
    }
    
    Listing 4-2c:メモリ安全性によるエラー
    $ rustc listing4_2c.rs
    error[E0382]: borrow of moved value: `s1`
     --> listing4_2c.rs:5:16
      |
    2 |     let s1 = String::from("hello");
      |         -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
    3 |     let s2 = s1;
      |              -- value moved here
    4 |
    5 |     println!("{s1}, world");
      |                ^^ value borrowed here after move
      |
      = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
    help: consider cloning the value if the performance cost is acceptable
      |
    3 |     let s2 = s1.clone();
      |                ++++++++
    
    warning: unused variable: `s2`
     --> listing4_2c.rs:3:9
      |
    3 |     let s2 = s1;
      |         ^^ help: if this is intentional, prefix it with an underscore: `_s2`
      |
      = note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
    
    error: aborting due to 1 previous error; 1 warning emitted
    
    For more information about this error, try `rustc --explain E0382`.
    

    invalidated reference (有効ではなくなった参照) を使ったためエラーとなる。

    Rust では、自動的にデータの deep copy が行われることは絶対にないので、自動コピーは効率的に行われる。

    Scope and Assignment

    この逆は、scoping (範囲), ownership (所有権), drop() 関数によって解放されるメモリの関係にもあてはまる。 存在する変数に全く新しい値を代入したとき、Rust は drop() 関数を呼び出して元のデータを直ちに解放する。

    Listing 4-2d: 変数sに新しい文字列を代入する
    fn main() {
        let mut s = String::from("hello");
        s = String::from("ahoy");
    
        println!("{s}, world");
    }
    

    Variables and Data Interactiong with Clone (複製による変数とデータの相互作用)

    String型のスタック上のデータだけではなく、ヒープ上のデータも deep copy したい場合は、共通のメソッド clone を使う。
    clone による変数とデータの相互作用
        let s1 = String::from("hello");
        let s2 = s1.clone();
    
        println!("s1 = {s1}, s2 = {s2}");
    
    これは正しく動作するが、そのコストは高い。

    integer を使う Listing 4-2 のコードは、clone関数の呼び出しがなくても正しく動作することに注意が 必要である。代入によってデータがそのままコピーされるからである。
    Listing 4-2: 変数x,yに整数値を代入する
        let x = 5;
        let y = x;
    

    Rust は Copy trait (トレイト、特性) とよばれる特別な annotation があり、 integer のようなスタックに配置されるデータ型に対して宣言できる。 データ型が Copy trait に適合していれば、代入後も古い変数が使用可能である。 データ型がその一部でも Drop trait を実装している場合は CopyM trait は宣言できない。 型の値が scope を出たときに何か特別なことをする必要がある場合は Code trait を宣言すると コンパイル時にエラーがでる。

    Ownership and Function (所有権と関数)

    関数に値を渡す機構は、変数に値を代入する機構と似ている。 関数に値を渡すと、代入と同じように movecopy を行う。
    Listing 4-3: ownershipとscopeが注釈された関数
    fn main() {
        let s = String::from("hello");      // s が scope に入る
    
        takes_ownership(s);                 // s の値が関数に move する
                                            // ... もはや valid ではない
        let x = 5;				// x が scope に入る
    
        makes_copy(x); 			// i32 は Copy trait を実装しているので、
                                            // x は関数へ move されることはなく、
                                            // これ以降で x を使っても大丈夫である。
    }  // x が scope からはずれる。sに関しては値が move されているので特別なことは何も起きない。
    
    fn takes_ownership(som_string: String) {  // some_string が scope に入る
        println!("{some_string}");
    } // some_string が scope からはずれるので, drop() が呼ばれて、メモリが解放される。
    
    fn makes_copy(some_integer: i32) {    	  // some_integer が scope に入る
        println!("{some_integer}");
    }                                         // some_integer が scope からはずれる
    

    Return Values and Scope (返り値とスコープ)

    Listing 4-4: 返り値の所有権を渡す
    fn main() {
    }
    
  3. References (参照) and Borrowing (借用)
  4. The Slice Type


http://ynitta.com