安全性、速度、並行性を兼ね備えた言語と、巷でうわさの「Rust」を覗いてみる(その3:所有権)
“うわさの「Rust」を覗いてみる”第三弾となる今回は、「The Rust Programming Language」を読みながら所有権の部分を覗いていきたいと思います。
所有権とは
ガベージコレクション(以下、GC)が無い言語では、自分でメモリを管理する必要があります。 シンプルなコードであれば管理しやすいですが、複雑なコードになると難しくなってきます。
それを自動的に管理してくれるのがGCなわけですが、Rustではメモリを自分で管理せずに、GCも使うことなく、 第三の新たな方法で管理します。
それが、所有権(Ownership)です。
新しい概念なので慣れまでに時間がかかるとのことですが、どんな仕組みなのか楽しみです。
この概念に触れるためにRustを覗き始めたようなものですから。
所有権のルール
所有権のルールは以下のように書かれています。
* There can only be one owner at a time.
* When the owner goes out of scope, the value will be dropped. https://doc.rust-lang.org/book/ より引用
一つずつ見ていきます。
Rustのそれぞれの値は所有者と呼ばれる変数がある
所有権ではなく所有者(owner)です。 所有者は変数みたいですね。
どんなときも所有者は一人である
1つの値に対して、必ず所有者は一人になるようです。
所有者がスコープ外になったとき、値は削除(drop)される
所有者は変数なのでスコープを持っているらしく、そのスコープの外に出ると、 対応している値は自動的に削除されるようです。
スコープを上手く使ってメモリをコントロールする感じなんでしょうか。
実際にコードで確認する
では、実際にコードを書いて見ていきたいと思います。
fn main() {
let s = String::from("hello");
println!("{}", s);
}
ヒープにメモリを確保するためにString型を使っています。
参考元のサイトによると変数 s 自体はスタックに保持され、内容の hello がヒープに入るようです。
なので、アロケートしたメモリを開放する必要があります。
変数 s は、main関数を抜けるとスコープ外になります。
そのときに、Rustはdrop関数を自動的に呼び出しOSにメモリを返還してくれます。
This function is called drop, and it’s where the author of String can put the code to return the memory.
Rust calls drop automatically at the closing curly bracket.https://doc.rust-lang.org/book/ より引用
便利ですね!
ムーブについて
次にムーブについて見ていきます。
fn main() {
let x = 5;
let y = x;
println!("{}", y); // 5
}
このコードは、 x をとおして y に5を設定しているコードです。 すべてスタックに積まれます。
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s2); // hello
}
では、String型の場合はどうでしょうか。
先ほどと同じく、 hello のデータ部分はヒープに入ります。
しかし、 s2 に代入しても、ヒープの中身はコピーされません。
つまり、 s1 と s2 からヒープ内の同じメモリ領域を参照している状態になるので、 s1 と s2 がスコープ外になるとRustはDrop関数を呼んで、二重解放してしまいそうなのですが・・・。
実際にはそうなりません。
s2 に代入したあとに s1 を参照しようとすると・・・。fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // hello
}
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:5:20
|
2 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
3 | let s2 = s1;
| -- value moved here
4 |
5 | println!("{}", s1); // hello
| ^^ value borrowed here after move
エラーになり、 s1 が s2 に「ムーブされた」ので s1 が使えないことをコンパイラが検出してくれます。
なので、二重解放が起きないわけです。
note: 「shallow copy」と「deep copy」と「move」
「shallow copy」は、以下の図のように s1 と s2 から同じヒープ領域を参照している状態です。
実際にデータをコピーしないので、コピーコストが低くなります。
一方、「deep copy」は、実際にデータをコピーします。
そして、最後に今回みたムーブ(move)は、 s1 を無効化します。
この記事を書いた人
-
2008年にアーティスへ入社。
システムエンジニアとして、SI案件のシステム開発に携わる。
その後、事業開発部の立ち上げから自社サービスの開発、保守をメインに従事。
ドメイン駆動設計(DDD)を中心にドメインを重視しながら、保守可能なソフトウェア開発を探求している。
この執筆者の最新記事
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー