[Rust] 参照、借用 – References, Borrowing

初めに

下記で所有権について簡単にまとめました。
所有権システムは、使いこなせれば良いコードを書けそうな予感があります。
ただ、所有権システムだけでは扱いづらいというのが正直な感想です。
そんなわけで、参照と借用について簡単にまとめます。
https://heterotaxy.blog/rust-%e6%89%80%e6%9c%89%e6%a8%a9-ownership/

参考リンク:
https://doc.rust-jp.rs/book-ja/ch04-02-references-and-borrowing.html

何が扱いづらいか?

下記の例を見ていただきたいです。

fn main() {
    let s = String::from("Hello, World!");
    let l = get_length(s);

    println!("The length  of {} is {}", s, l);
}

fn get_length(s: String) -> usize {
    s.len()
}
実行時のエラー

ちなみにキャプチャには、載せていませんが、エラーの続きにはnoteやhelpでエラーの解消方法も掲示してくれます。改めて親切な言語だと思います。

所有権のみでは、関数を呼び出した後に、呼び出し元では所有権がなくなるので無効になります。引き続き利用したい時などとても不便です。get_length関数で長さだけではなくsも一緒に返せば良いと思いますが、少し大袈裟すぎるので避けたいです。

そこで、役に立つのが今回の内容かなと思います。

参照 – References

他の言語をいじったことがある人は、馴染み深い参照渡のようなものです。
値そのものではなく、値があるアドレスを渡す方法です。
Rust風にいうのであれば、「オブジェクトの所有権の譲渡ではなくオブジェクトへの参照を渡す」という表現が近いと思います。

利用法は、簡単で関数の引数に&をつけることで表現できます。
, +部分は差分です。実行してみるときは、文頭の, +は消してください。

fn main() {
    let s = String::from("Hello, World!");
-    let l = get_length(s);
+    let l = get_length(&s);

    println!("The length  of {} is {}", s, l);
}

- fn get_length(s: String) -> usize {
+ fn get_length(s: &String) -> usize {              // String →&String
    s.len()
}

これで、関数に値を渡しやすくなりましたかね、、、
これにより、所有権を戻す目的で値を呼び出し元に返す必要はなくなるのでありがたいですね。

借用 – borrowing

借用とは、なんぞと思いますが視点の違いによる表現が異なっているだけです。
呼び出し時に渡すときは、参照を渡しているので参照渡ですが、
視点を変えて呼び出す側から見ると借りているため、借用という表現になっている認識です。

この節のまとめるスコープは、借用した値は変更可能か?です。

他の言語を触ったことがある方であれば、馴染み深いかもしれないですが、
デフォルトで変更できる言語が多い認識です。
参照渡の値を変更すると呼び出しもとの値も変わる初学者を惑わす曲者です。
テストに出ますよね、、、C言語の平文を問題文に書くなよ。とか思いながら解いていました。

Rustでは、どうやらデフォルトで変更できないようになっているようです。
サンプルコードとエラーコードを見てみます。

fn main() {
    let s = String::from("Hello, World");
    change(&s);
}

fn change(s: &String) {
    s.push_str("!!");
}

実行してみると下記の結果になります。
ざっくりいうと、ただの参照では変更だめですよと言われています。
変更したければ、mutをつけてねと指摘されています。

エラーコードを頼りに変更したものが下記となります。

fn main() {
-    let s = String::from("Hello, World");
-    change(&s);
+    let mut s = String::from("Hello, World");
+    change(&mut s);
}

- fn change(s: &String){
+ fn change(s: &mut String) {
    s.push_str("!!");
}

note:
借用した値は、基本的に可変ではない。
借用した値に変更を加えた場合は、可変参照(mut)を付与する。

可変参照 – mutable

上記で話が出てしまったので、簡単にまとめます。
大きな制約と付随した利点を紹介します。

制約:
 特定スコープかつ特定データに対して一つの可変参照を持てない

この制約は、下記のデータ競合をコンパイル時に防ぐことができるので、大きな利点でもありそうです。これにより、コンパイル時点でデータの競合によるよくわからない振る舞いを防ぐことができるのは嬉しいです。

  • 2つ以上のポインタが同じデータにアクセスする。
  • 少なくとも一つのポインタがデータに書き込みを行っている。
  • データへのアクセスを同期する機構が使用されていない。

サンプルコード+エラーで例を見ます。

ケース1: 制約をもろに違反してみる。

fn main(){
    let mut s = String::from("Hello, World");
    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}
実行時のエラー

1回だけだよとエラーで教えてくれています。
それにしても、エラーが丁寧ですね、、、
大学の自分にこの言語を教えてあげたいですね、初学者に勧めたいかもしれないです。

ケース2: 不変と可変参照を組み合わせてみる。

fn main(){
    let mut s = String::from("Hello, World");
    let r1 = &s;
    let r2 = &s;
    let r3 = &mut s;

    println!("{}, {}, {}", r1, r2, r3);
}
実行時のエラー

不変参照の後に、可変参照で呼び出せれているので怒られています。
ちなみに順番を逆にしてみると
可変参照の後に、不変参照で呼び出されるので怒られます。
 余裕があれば実行してみルト良いと思います。

さいごに

本日は、参照の基礎的な部分をまとめました。
他にも参照外やらライフタイムなど他にもRustには用意されています。
気長にまとめていこうと思います。