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

本文へ

フッターへ

お役立ち情報Blog



安全性、速度、並行性を兼ね備えた言語と、巷でうわさの「Rust」を覗いてみる(その3:所有権)

“うわさの「Rust」を覗いてみる”第三弾となる今回は、「The Rust Programming Language」を読みながら所有権の部分を覗いていきたいと思います。

所有権とは

ガベージコレクション(以下、GC)が無い言語では、自分でメモリを管理する必要があります。 シンプルなコードであれば管理しやすいですが、複雑なコードになると難しくなってきます。
それを自動的に管理してくれるのがGCなわけですが、Rustではメモリを自分で管理せずに、GCも使うことなく、 第三の新たな方法で管理します。

それが、所有権(Ownership)です。

新しい概念なので慣れまでに時間がかかるとのことですが、どんな仕組みなのか楽しみです。
この概念に触れるためにRustを覗き始めたようなものですから。

所有権のルール

所有権のルールは以下のように書かれています。

* Each value in Rust has a variable that’s called its owner.
* 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にメモリを返還してくれます。

Rust calls a special function for us.
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 を無効化します。

この記事を書いた人

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

FOLLOW US

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