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外に移動したときには何も開放されない。

    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`.
    

    Scope and Assignment

  3. References (参照) and Borrowing (借用)
  4. The Slice Type


http://www.ynitta.com