やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustの所有権(関数)

 今回はたぶんメッチャ大事な回。

成果物

参考

参照と借用

所有権のムーブで使えなくなるケース

fn main() {
    let s = String::from("hello");
    let len = calc_len(s); // sの所有権がmain関数スコープのsからcalc_len関数スコープのsへムーブ
    println!("{}.", s1); // 所有権がないので参照不可!
}
fn calc_len(s: String) -> usize {
    s.len()
} // sは所有権をもつスコープの末端に来たので無効化(メモリ解放)

参照(借用)にて所有権のムーブをさせない

fn main() {
    let s = String::from("hello");
    let len = calc_len(&s); // &で参照する(借用)
    println!("{}.", s1); // 所有権はmain関数のままなので参照可
}
fn calc_len(s: &String) -> usize {
    s.len()
}

 借用すればcalc_len関数の終わりでメモリ解放させずにs変数を利用できる。

参照

 参照とは&記号である。所有権をムーブすることなく変数の値を参照できる機能。ただし変更はできない。

 読取専用のポインタみたいなものだと思われる。

借用

 借用とは、関数の引数に参照をとること。

可変な参照

let mut s = String::from("hello");
let r1 = &mut s;

 mutでmutable(可変)にできる。

可変参照は1つのみ

let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s; // error[E0499]: cannot borrow `s` as mutable more than once at a time

 生きている可変参照は1つでなければならない。上記はエラー。

 スコープが終わった後なら問題ない。以下はOK。

let mut s = String::from("hello");
{
    let r1 = &mut s;
} // r1はここでスコープを抜けて無効化(メモリ解放)されるので、以降はsの可変参照を作れる
let r2 = &mut s;

 また、不変で借用されているのに可変で借用しようとしたらエラーになる。これは不変であることを保証するためなので自然。

let mut s = String::from("hello");
let r1 = &s; // OK
let r2 = &s; // OK
let r3 = &mut s; // error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable

 これらの制約は既存のメモリ操作に慣れたプログラマには苦労する原因になりそう。

ダングリングポインタの防止

 メリットもある。

 ダングリングポインタとは無効なメモリ領域を指すポインタのこと。別の箇所で解放されたメモリ領域を、解放されたと知らずに参照してしまうポインタのこと。

 C/C++では意図せず発生してしまいうるが、Rustでは起こりうるコードを書くことはできずコンパイルエラーになる。

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String { // error[E0106]: missing lifetime specifier
    let s = String::from("hello");
    &s
} // 変数s無効化(メモリ解放)

 「ライフタイム指定子がない」という謎のエラー。それは別の章でやるらしいので無視。重要なのは変数sのスコープ。dangle関数で宣言したので同関数が終わると解放される。その前に解放された変数のポインタを返している。つまり、無効なメモリ領域を指すポインタを返してしまっている。

 Rustではコンパイルエラーになるため、動作すらさせず未然に危険なメモリアクセスを防いでくれる。これがもしC/C++なら実行時に強制終了されることになるはず。

 解決するにはString型変数を返せばいい。関数で戻り値を返すことは所有権のムーブがなされる。よってメモリ解放は戻り値を受け取った側のスコープ終端になる。

fn dangle() -> String {
    let s = String::from("hello");
    s
}

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで