[Rust] 所有権 – ownership

はじめに

お疲れ様です。
Rustの使い方をざっと見ていました。
そんな中で、若干迷いそうなものがあったので簡単にまとめようと思います。
正直下記を見ればなんとなくわかるので、時間があれば下記に目を通すのが良いかと思います。

参考リンク:
https://doc.rust-jp.rs/book-ja/ch04-00-understanding-ownership.html

メモリについて

コンピュータプログラムは、内部のスタックやらヒープやらにデータを格納したり、時には解放したりすることで実行しています。データを格納してばかりでは、メモリが足りなくなるので解放しなくてはいけない、という大まかな認識でひとまず良いと思います。
この解放という一手間を他の言語では、明示的にメモリを確保したり解放することや、GCのように定期的に利用されていないメモリを探索して解放するような仕組みを設けています。

一方、Rustは別の方法を使っているようです。
メモリをコンパイラがコンパイル時にチェックする規則と今回話題の所有権を通して管理しています。
しかも、嬉しいことに所有権が原因でプログラム実行の遅延に影響を与えないらしいです。

「所有権とはRustにおけるメモリ管理の手法である」という解釈かと思います。

所有権

変数がどこのスコープに属しているのか?って観点で読みすすめると良いかもしれないです。

変数スコープ

ここの部分は、基本的な感じなので問題ないです。

fn main() {
    if true {                    // sは定義前、sは有効ではない
        let s = "hello";
        println!("{}", s);
    }                            // このスコープは終わり。もうsは有効ではない
    println!("{}", s);           // エラー
}

実行してみるとこんなな感じ、Rustのエラーは丁寧ですね。感動しました。

メモリの解放は、スコープを抜けるときに自動で実施してくれるようです。

所有権+代入

初見だと、おやっ?となりそうです。
下記の実行結果はなんでしょうか?

fn main() {
    let s1 = String.from("Hello");
    let s2 = s1;
    
    println!("{}, world!", s1);
}

“Hello, world!”って叫びたいですが、実際はエラーが出ます。
ムーブの後に借りた?

参考リンクの画像を拝借、、、
s1s2のメモリの参照先が同じになります。
もし、s1s2がスコープを抜けるとどうなるか考えてみるといいと思います。
s1s2の両方に対してメモリの解放が行われるため、同一メモリに対して2回の解放処理が行われることになります(多分エラーが生じる?)。これを防ぐために移動した後s1は利用できなくすることでこれを避けているようです。

(あっ、moveは所有権の移動か、、)

所有権+関数

関数に所有権を渡すと代入と同じく、移動やコピーが行われるようです。

fn main() {
    let s = String::from("my ownership");  // sの定義
    takes_ownerships(s);                   // 所有権の移動
    println!("{}", s);                     // エラー
}                                          // sの所有権はtakes_ownershipへ移動している
fn takes_ownership(test: String){        // testがスコープに入る
    println!("{}", test);
}                     // testが解放される

所有権が移動していますね、

所有権+戻り値

関数の戻り値に対しても、移動が行われるようです。
ここまで来れば、所有権の動作もなんとなくつかめてくるかもしれないです。

fn main() {
    let s = String::from("my ownership");  // sの定義
    let s1 = gives_ownership(s);           // sの所有権が移動し、戻り値の所有権くる
    println!("{}", s1);
    println!("{}", s);                     // エラー  
}

fn gives_ownership(test: String) -> String{// testがスコープに入る
    println!("{}", test);
    test                                   // 所有権が呼び出し元へ戻る
}

sは所有権が移動してしまっているので

さいごに

所有権について、割とすんなり理解できた気がします。
これがRustの基本的な考え方になりそうなので、早く慣れたいです。

今回、所有権の移動しか書いていないですが整数形や理論値形などは所有権の移動ではなくコピーになるのでここは注意です。ひとまず、スカラー値かどうかで区別できそうです。