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

本文へ

フッターへ

お役立ち情報Blog



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

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

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

ダングリング参照(Dangling References)

ダングリングポインタは、無効なメモリ領域を指す危険なポインタです。

Rustでもダングリングポインタのようなダングリング参照を発生させるコートをかけますが、コンパイラは、参照より先に、元データがスコープ外(dropされる)にならないことを保証してくれます。

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

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

    &s
}
 dangle 関数は、変数 s の参照を返します。
変数 s への参照は、 main 関数に返されますが、変数 s 自体は、 dangle 関数のスコープを抜けた時にdropされます。
よって、 main 関数に返される参照は、ダングリング参照になります。
ですので、コンパイラが検出してエラーにしてくれています。

error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn dangle() -> &'static String {
  |                 +++++++

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

エラーには、「関数の戻り値の型が借用になっているが、借用するための値が存在しない」という内容が書かれています。

参照の安全性

ここで少し教科書を離れ、「プログラミングRust 第2版」(O’Reilly)の、5.3「参照の安全性」を副読します。

fn main() {
    {
        let r;
        {
            let x = 1;
            r = &x;
        }
        assert_eq!(*r, 1); // bad: reads memory `x` used to occupy
    }
}

この場合のエラーは以下の通りです。

>error[E0597]: `x` does not live long enough
 --> src/main.rs:6:17
  |
6 |             r = &x;
  |                 ^^ borrowed value does not live long enough
7 |         }
  |         - `x` dropped here while still borrowed
8 |         assert_eq!(*r, 1); // bad: reads memory `x` used to occupy
  |         ----------------- borrow later used here

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

こちらも、先ほどの例とおなじく、変数xがdropされるのに、その参照をスコープの外側で使おうとしているので、 ダングリング参照になります。(書籍の中では、ダングリングポインタと書かれています。)

そして、どのようにダングリング参照を見つけているのかが図解されています。

Rustコンパイラは、プログラム中のすべての参照型に対して、その参照の使われ方によって生じる制約を反映した生存期間(lifetime)を割り当てる。生存期間とは、プログラム実行中の、ある参照が安全に利用できる期間を指す。「プログラミングRust 第2版」(O'Reilly)より引用。

Rustは、参照型に対して、その参照が安全に利用できる期間を、生存期間(lifetime)として割り当てています。

それでは図を見ていきましょう。(下記の図はすべて、プログラミングRust 第2版に記載されている図を引用したものです。)

まず、 &x に対して許される生存期間をコンパイラは見つけるようです。

次に、 r に対して許される存在期間を見つけて、

その範囲を比較するようです。

図をみれば、なるほどと納得できますが、これを「実装しろ!」と言われると、できる気がしないやつです。

実はこの説明は、「The Rust Programming Language」でも書かれています。
(参照:https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html

図が分かりやすかったので、「プログラミングRust 第2版」を取り上げました。

ライフタイムについても調査したいのですが、長くなりそうなので次回にします。

参照の比較

こちらも「プログラミングRust 第2版」からの内容です。

fn main() {
    let x = 10;
    let y = 10;
    let rx = &x;
    let ry = &y;
    let rrx = &&℞
    let rry = &&&ry;
    
    assert!(rrx <= rry);
    assert!(rrx == rry);
    assert!(!std::ptr::eq(rrx, rry));
}

上記のコードのアサーションは成功します。
変数 x  y は、同じ「10」という値ですが、アドレスは違います。

しかし、「<=」演算子や、「==」演算子は、参照解決した先の値のみの比較を行うのでアサーションが成功します。
 std::ptr:eq でアドレスの比較ができるので、最後のアサーションでアドレスが異なることを確認しています。

では、つぎのコードはどうでしょうか。

fn main() {
    let x = 10;
    let y = 10;
    let rx = &x;
    let ry = &y;
    let rrx = &&rx;
    let rry = &&&ry;
    
    assert!(rrx <= rry);
    assert!(rrx == rry);
}

分かりにくいですが、変数 rrx を、 &&&rx ではなく、 &&rx に変更しました。

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

error[E0277]: can't compare `{integer}` with `&{integer}`
 --> src/main.rs:9:17
  |
9 |     assert!(rrx <= rry);
  |                 ^^ no implementation for `{integer} < &{integer}` and `{integer} > &{integer}`
  |
  = help: the trait `PartialOrd<&{integer}>` is not implemented for `{integer}`
  = help: the following other types implement trait `PartialOrd<Rhs>`:
            f32
            f64
            i128
            i16
            i32
            i64
            i8
            isize
          and 6 others
  = note: required because of the requirements on the impl of `PartialOrd<&&{integer}>` for `&{integer}`
  = note: 2 redundant requirements hidden
  = note: required because of the requirements on the impl of `~const PartialOrd<&&&&{integer}>` for `&&&{integer}`

error[E0277]: can't compare `{integer}` with `&{integer}`
  --> src/main.rs:10:17
   |
10 |     assert!(rrx == rry);
   |                 ^^ no implementation for `{integer} == &{integer}`
   |
   = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}`
   = help: the following other types implement trait `PartialEq<Rhs>`:
             f32
             f64
             i128
             i16
             i32
             i64
             i8
             isize
           and 6 others
   = note: required because of the requirements on the impl of `PartialEq<&&{integer}>` for `&{integer}`
   = note: 2 redundant requirements hidden
   = note: required because of the requirements on the impl of `~const PartialEq<&&&&{integer}>` for `&&&{integer}`

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

変数 rrx  rry の型が異なる場合は、比較ができないようです。

fn main() {
    let x = 10;
    let y = 10;
    let rx = &x;
    let ry = &y;
    let rrx = &&rx;
    let rry = &&&ry;
    
    assert!(rrx <= *rry);
    assert!(rrx == *rry);
}

なので、変数 rry をデリファレンスすると比較可能になります。

この記事を書いた人

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

FOLLOW US

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