安全性、速度、並行性を兼ね備えた言語と、巷でうわさの「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
}
変数 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版に記載されている図を引用したものです。)
まず、 &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 = &℞
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 = &℞
let rry = &&&ry;
assert!(rrx <= *rry);
assert!(rrx == *rry);
}
なので、変数 rry をデリファレンスすると比較可能になります。
この記事を書いた人
-
2008年にアーティスへ入社。
システムエンジニアとして、SI案件のシステム開発に携わる。
その後、事業開発部の立ち上げから自社サービスの開発、保守をメインに従事。
ドメイン駆動設計(DDD)を中心にドメインを重視しながら、保守可能なソフトウェア開発を探求している。
この執筆者の最新記事
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー