借用(shared borrowing)のルール

参考資料:Shared borrows - YouTube

shared referenceともいう。借用(borrowing)は所有権(ownership)を移転せずに変数を持ち回るための要素技術である*1。コピーを作らないためメモリに優しいが、いくつかのルールが存在する。以下、簡単なコードをもとに、借用の基本的なルールを見ていこう。

let name = format!("hello");
let r = &name; // 借用。参照をつくる
helper(r);
fn helper(name: &String) { // ← 参照を文字列に変えている
    println!(..);
}

shared referenceは不変なのでデータそのものに変更を加えられない。以下のように、データに変更を加えるようなコードはコンパイル時に弾かれる。

fn helper(name: &String) {
    name.push('x');
}

read only, no writeであることを確認した。

さて、次のコードから借用について考えていこう。。

fn main() {
   let name = format!("fellow Rustaceans");
   let r = &name;
   helper(r);
   helper(r);
}

fn helper(name: &String) {
    println!("Hello, {}!", name);
}

nameに変更を加えたいので可変(mut)にし、なんか変えて(pushして)みる。

fn main() {
   let mut name = format!("fellow Rustaceans");
   name.push('x');

   let r = &name;
   helper(r);
   helper(r);
}

このコードは正しいコードであり、コンパイルされる。shared referenceは不変でなければならない、とさっき言ったが、どうしてname.push('x')しているこのコードは正しいといえるのか。実は、Rustの変数に純粋な不変状態と可変状態というのは存在せず、それは時間に依存するものであるらしい*2。つまりlet mut nameは宣言された時点ではnameは可変、そして借用が行われた(let r = &name)時点で、nameは不変になる。

上に述べたことをコードで確かめてみる。

fn main() {
   let mut name = format!("fellow Rustaceans");

   let r = &name;
   name.push('x');
   helper(r);
   helper(r);
}

これは前述のコードの、name.push('x')行を借用(let r = &name)の後に持ってきただけである。このコードをコンパイルすると、次のエラーが出る。

error[E0502]: cannot borrow `name` as mutable because it is also borrowed as immutable
 --> /Users/***/shared_borrow.rs:4:4
  |
3 |    let r = &name;
  |             ---- immutable borrow occurs here
4 |    name.push('x');
  |    ^^^^ mutable borrow occurs here
...
7 | }
  | - immutable borrow ends here

error: aborting due to previous error

このエラーは「あなたはnameを可変なものとして使おうとしていますが、nameは(参照を作ったことにより)借用されたので、変更することはできません」ということを言っている。

ちなみに以下のようなコードもエラーである。

fn main() {
   let mut name = format!("fellow Rustaceans");

   let r = &name;
   helper(r);
   helper(r);
   name.push('x');
}

人間から見ればhelperによるrの借用は終了しているので、最後のname.push('x')は問題ないように見えるが、Rustのコンパイラはそこまで面倒を見てくれない。

これを回避するには次のようにする。

fn main() {
    let mut name = format!("fellow Rustaceans");

    {
        let r = &name; // ←借用開始
        helper(r);
        helper(r);
    } // ←借用終了

    name.push('x');
}

つまり、カーリーブレース{}で借用している範囲を囲むのである。このようにするとコンパイラに変数を借りている範囲(スコープ)を明示することができる。

*1:Rustに固有の用語は翻訳しても仕方ない気がするが、一応ここではborrowingの訳語を「借用」、ownershipを「所有権」としておく。また本記事では所有権の説明は行わない

*2:参考資料は上の動画ですが、ここの部分の意訳の正確性に自信がないです…