グローバルナビゲーションへ

本文へ

フッターへ

お役立ち情報Blog



安全性、速度、並行性を兼ね備えた言語と、巷でうわさの「Rust」を覗いてみる(その7:参照と借用)

みなさまお久しぶりです。

今回も「The Rust Programming Language」を読みながら、参照と借用についてみていきたいとおもいます。

可変の参照の制約

前回は、可変の参照を利用しました。

この可変の参照は重要な制約があるようなので、それを見ていきます。

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}

このコードを実行すると以下のエラーが出力されます。

error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.

特定の値への可変の参照を持つと、その値への他の可変参照を持つことができません。
つまり、特定の値への1つ以上の可変参照は作れないことになります。

なぜ制約があるのか

ではなぜ、このような制約があるのでしょうか。

A data race is similar to a race condition and happens when these three behaviors occur:

Two or more pointers access the same data at the same time.
At least one of the pointers is being used to write to the data.
There’s no mechanism being used to synchronize access to the data.

The Rust Programming Language

データの競合(data race)は、競合状態(race condition)と似ていて、以下の3つの動作が発生したときに発生します。

  1. 2つ以上のポインターが同時に同じデータにアクセスする。
  2. 少なくとも1つのポインターがデータへの書き込みに使用されている。
  3. データへのアクセスを同期させるためのメカニズムがない。

複数のポインタが、同じものに書き込みしていると、それらを同期させる方法がないと確かに競合が発生しそうです。
この簡単な解決法としては、1つずつ順番に処理する事でしょうか。

すなわち、可変の参照を同時に複数作れないという制約になるのかと思います。

実際に試してみる

この3つの条件を読んで思ったのが、「可変の参照じゃなければいいのか?」ということです。

実際に試してみます。

すべて不変の借用の場合

fn main() {
    let s = String::from("hello");

    let r1 = &s;
    let r2 = &s;

    println!("{}, {}", r1, r2);
}

すべて不変の借用の場合は問題ありません。

可変と不変の借用を混ぜた場合

つぎは、可変と不変の借用を混ぜた場合です。

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &s;

    println!("{}, {}", r1, r2);
}

こちらは、以下のエラーが出力されます。

error[E0502]: cannot borrow `s` as immutable because it is also borrowed as mutable
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ mutable borrow occurs here
5 |     let r2 = &s;
  |              ^^ immutable borrow occurs here
6 |
7 |     println!("{}, {}", r1, r2);
  |                        -- mutable borrow later used here

For more information about this error, try `rustc --explain E0502`.

可変で借用しているので、不変では借用できないようです。

順序を逆にした場合

順序を逆にしたらどうでしょうか。

fn main() {
    let mut s = String::from("hello");

    let r2 = &s;
    let r1 = &mut s;

    println!("{}, {}", r1, r2);
}

こちらもエラーになります。

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:5:14
  |
4 |     let r2 = &s;
  |              -- immutable borrow occurs here
5 |     let r1 = &mut s;
  |              ^^^^^^ mutable borrow occurs here
...
8 |     println!("{}, {}", r1, r2);
  |                            -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.

つまり、可変と不変の借用は同時にできない事になります。

その理由は、データの競合をコンパイラで防ぐためだと思われます。

参照の参照

少し気になったので、参照の参照を作ってみました。

fn main() {
    let s = String::from("hello");

    let r1 = &s;
    let r2 = &&s;
    let r3 = &&&&&&&&&&s;

    println!("{}, {}, {}", r1, r2, r3);
    
    let l = calculate_length(r3);
    
    println!("{}", l);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

動かなさそうな予想と反してエラーになりませんでした。

どうやら s.len() の「.」演算子が、メソッド呼び出しの際に左オペランドを参照解決してくれるようです。
println!マクロも、「.」演算子をつかうコードに展開されるので、こちらも参照解決されます。

日常的に参照を利用することを想定していることが、この仕様から理解できます。

この記事を書いた人

tkr2f
tkr2f事業開発部 web application engineer
2008年にアーティスへ入社。
システムエンジニアとして、SI案件のシステム開発に携わる。
その後、事業開発部の立ち上げから自社サービスの開発、保守をメインに従事。
ドメイン駆動設計(DDD)を中心にドメインを重視しながら、保守可能なソフトウェア開発を探求している。
この記事のカテゴリ

FOLLOW US

最新の情報をお届けします