やっつけな日常(29) Rustに入ればRustに従え、「所有権」を「借用」できたのね

Joseph Halfmoon

所有あれば借用もあり。Rust言語は現実的です。前々回は「特定のトレイトの標準的な実装」を継承することで構造体のクローンを作ってみました。クローンは元の構造体とは別な実体なので所有権も別、後はご勝手。その一方、今回は所有権を「借用」して、構造体を参照するどころか書き換えまでやってみます。やればできる!ホントか?

いつもお世話になっております「The Rust Programming Language 日本語版」の以下のページを「参照」させていただいとります。

参照と借用

Rustにも「参照」あるのね。それどころか「借用」って何?またもや上記ページから1か所引用させていただきます。

関数の引数に参照を取ることを借用と呼びます。

英文みると borrowing のようです。なんだ所有権を移転(MOVE)したり、構造体をクローンしたりしなくてもアクセスできるんじゃん、出来るなら早く言ってよ、って感じ。

実験してみる3つの関数

今回Rust処理系にコンパイルしてもらって実験してみるのは以下の3つの関数です。

    1. add11 前回使用の関数。ミュータブルな構造体を引数にとり(その時点で所有権はmoveされる)、構造体メンバを書き換えたのち返却(戻り先に所有権が再びmoveされる)するもの
    2. add11_borrow 構造体の参照を引数にとり(borrowing)、関数内で読み取り使用のみするもの
    3. add11_borrow_mut 構造体の参照、ただしミュータブル宣言しているもの、を引数にとり(borrowing)、関数内で構造体を書き換えてしまうもの。

move_borrow

こいつらを呼び出す度に、呼び出し元の関数で元の構造体がどうなっているのか見て行きたいと思います。

実験に使ったRustのコード全文

コンパイルして実行可能コードが以下に。敢えてエラーを噛み締めたいところはコメントとして残してあります。

struct Dual {
    a: i32,
    b: i32
}

fn add11(mut arg: Dual) -> Dual {
    println!("move: a={:?} b={:?}", arg.a, arg.b);
    arg.a += 11;
    arg.b += 11;
    return arg;
}

fn add11_borrow(arg: &Dual) {
    println!("borrow: a={:?} b={:?}", arg.a, arg.b);
}

fn add11_borrow_mut(mut arg: &mut Dual) {
    println!("borrow mut: a={:?} b={:?}", arg.a, arg.b);
    arg.a += 11;
    arg.b += 11;
    println!("borrow mut: a={:?} b={:?}", arg.a, arg.b);
}

fn main() {
//  let test = Dual { a:33, b:44 }; <== add11ではOK, add11_brrow_mutではNG
    let mut test = Dual { a:33, b:44 };
    println!("  1: a={:?} b={:?}", test.a, test.b);

    add11_borrow(&test);
    println!("  2: a={:?} b={:?}", test.a, test.b);

    add11_borrow_mut(&mut test);
    println!("  3: a={:?} b={:?}", test.a, test.b);

    let test2 = add11(test);
    println!("  4: a={:?} b={:?}", test2.a, test2.b);
//  println!("a={:?} b={:?}", test.a, test.b); <== ERROR
}
実行結果

VSCode内から実行してみた結果が以下に。

results

 

「1:」のところは、普通に構造体を初期化してそのメンバa, b をプリントしてます。

その後、この構造体を「借用」した関数内で構造体を参照し、a, b をプリントしています。当然値は同じ。

関数からもどって「2:」のところで構造体を確認しています。もし「借用」でなく「所有権の移転」ならばこのアクセスはエラーになる筈ですが、「借用」なので問題なし、と。

次に、同じ構造体をミュータブルと宣言した上で「借用」する関数内で、構造体を参照し、a, b をプリント。まだ値は同じ、そして値に+11して関数内でa、bを再びプリント。+11した値が印字できてます。

そして、関数から戻ってきて「3:」で構造体を確認しています。借用先での書き換えを行った(許可しているし)ので、値書き換わってます。

最後、前々回も使用した所有権移転をともなう関数で値を確認後、再び+11してみます。そして書き換えた構造体を所有権移転をともなう形で返却します。返却先は元の変数とは別の新しい変数です。よって返却先の新しい変数を使ってアクセスする分には値が読み取れますが、以前の変数は所有権が無いので使うとエラーで落ちます。

なお、所有権移転をともなう場合、元の構造体宣言は mut していなくても移転先の関数で mut としておれば書き換え可能でした。しかし 借用の場合、元の構造体がミュータブルなのかイミュータブルなのかがそのまま有効になるので、元の構造体が mut でないとエラーになります。

また所有権移転がともなう場合は所有権を返してやらないとアクセスできないですが、ミュータブルな借用の場合、所有権は元に残ったままなので何もせずともアクセスできました。

なんだか複雑だな~、人生に似ている? 人生、複雑にしたくないモンでありますが。

やっつけな日常(28) Raspberry Pi OS、32bit版から64bit版へ移行 へ戻る

やっつけな日常(30) Rustに入ればRustに従え、モジュールを別ファイルにするお作法