3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

スマートポインタとしてのBox

Rustでは、プログラム実行中にデータをメモリに格納するとき、状況に応じてスタックかヒープのどちらかに格納します。
スタックは出し入れが高速な一方で、データの型(すなわち、確保しなければならないデータ領域のサイズ)が定まっている必要があります。ヒープは可変サイズのデータも格納できる一方、アクセス速度はスタックに劣ります。

デフォルトでは可能な限りデータはスタックに格納されます。もしデータを明示的にヒープに格納したい場合は、スマートポインタのBoxを利用できます。データの本体はヒープに置きつつも、そのデータへの参照をBoxとして宣言すれば、その参照自体はスタックに積むことができます(なぜなら、参照自体は固定長だから)。

Boxの使い方

Box::new(値)で作成できます。

fn main() {
    let x = 5; // スタックに格納
    let y = Box::new(5); // ヒープに格納

    println!("x = {}", x);
    println!("y = {}", y);
}

Boxを使うユースケース:再帰的なデータ構造

Boxを使ってあえて明示的にヒープ領域を使うように指示するユースケースとして、再帰的なデータ型(構造体)を定義したいときがあります。
ある構造体のプロパティにその構造体自身が含まれている場合(構造体のネストがあり得る場合)、構造体のネストがどれだけ続くか分からず、構造体のデータサイズが決定できません。そこで、構造体そのものを含むのではなく、構造体へのポインタを含むように定義することで、構造体自身のサイズは固定になります。

enum List {
    Node(i32, Box<List>),
    Nil,
}

fn main() {
    let list = List::Node(1, Box::new(List::Node(2, Box::new(List::Nil))));
}

上記の例では、Listは再帰構造になっていますが、Node(i32, Box<List>)のようにBox<T>を使ってスマートポインタを含むようにしています。もしこれをNode(i32, List)のようにしてしまうと、コンパイラエラーになります。

*で行うDeref

ポインタに*をつけて参照先の値を取得する操作をderefといいます。Boxはスマートポインタでポインタの一種であるため、derefすることができます。

fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, *y); // Derefを使用して値にアクセス
}

*をつけてderefできることは、厳密にいえばDerefトレイトを実装していることと等しいです。言い方を変えれば、Derefトレイトを実装することで、独自の型であっても*を使ってderefすることができます。

独自にDerefを実装する

Derefを実装するには、以下の定義が必要です。

  • 関連型Target
  • Targetを返すderefメソッド

関連型とは、トレイトが要求する型で、トレイト実装のために必要なメソッド(今回でいえばderef)のシグネチャに必要な型になります。derefメソッドによってどのような型が返却されるかは、実装先の構造体がどのような型を返すつもりなのかによって決まります。そのため、実装先の構造体にTargetという型を決めさせておき、それを返すようにderefを定義しています。

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn main() {
    let x = 5;
    let y = MyBox::new(x);

    assert_eq!(5, *y); // Derefトレイトにより値にアクセス
}

分かりにくいですが、MyBoxはタプル構造体になっています((T))。そのため、値は&self.0から取得できます。

参照外し型強制

Derefトレイトを実装するメリットの一つに、参照外し型強制があります。Box::newを使って作成したスマートポインタの型はBox<T>になります。
例えばある関数が&i32を要求しており、その関数の引数にBox<i32>を渡したいとします。Box<i32>をderefすることでi32を取り出す必要があるように思えますが、実際はその操作をせずともコンパイルに成功します。

fn print_value(x: &i32) {
    println!("{}", x);
}

fn main() {
    let value = Box::new(42); // Box<i32>
    print_value(&value);      // Rustが自動的に参照外しして&Box<i32> -> &i32に変換
}

これは参照外し型強制という仕組みで、Derefトレイトが実装されている型を関数の引数に使うと、引数として要求されている型になるまで自動でderefを繰り返してくれる機能です。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?