ユーニックス総合研究所

  • home
  • archives
  • rust-box

RustのBoxの使い方【ヒープにメモリを確保!初心者向け】

  • 作成日: 2022-05-10
  • 更新日: 2023-12-25
  • カテゴリ: Rust

RustのBoxの使い方

RustにはBoxというオブジェクトがあります。
これを使うとオブジェクトのメモリをヒープに確保することができます。

Rustで高度なプログラミングをする場合、Boxは必須になってきます。
今回はこのBoxについて解説します。

関連記事

文字列の描画 - Rustで作るWindowsアプリ
改行付きの文字列を描画する - Rustで作るWindowsアプリ
ラベルの表示 - Rustで作るWindowsアプリ

文字列の描画 - Rustで作るWindowsアプリ
改行付きの文字列を描画する - Rustで作るWindowsアプリ
ラベルの表示 - Rustで作るWindowsアプリ

解説の前提とする構造体Point

今回、記事でよく使う構造体がこのPointです。
Pointはゲームや描画ソフトなどで座標を表すのに使う構造体、という想定です。

struct Point {  
    x: i32,  
    y: i32,  
}  

座標の型はi32になっています。

スタック領域とヒープ領域

メモリには大きく分けてスタック領域ヒープ領域があります。
スタックは自動変数などが確保される領域です。
通常、Rustで変数を作った場合はこちらのスタック領域にメモリが確保されます。

いっぽうヒープ領域とは、動的なメモリのことを言います。
メインメモリから「これぐらい頂戴」と言ってメモリを渡してもらい、それを使います。

一般的にはスタック領域よりもヒープ領域のほうが大きなメモリを確保することができます。
動画作成ソフトなどメモリをたくさん使うソフトを作りたい場合はこのヒープ領域について理解しておく必要があります。

C言語などではヒープ領域のメモリはmalloc()などの関数で確保しますが、それに相当するのがRustのBoxと言えます。

Boxの使い方

Boxは型を指定して使います。
たとえば変数を定義したい時は↓のようにBoxに型を定義します。

    // Boxを使ってPointをヒープに確保する  
    let p0: Box<Point> = Box::new(Point {  
        x: 1,  
        y: 2,  
    });  

    println!("{} {}", p0.x, p0.y);  // 1 2  

Box::new()に構造体などの式を渡すと、それについてBoxが作成されます。
↑の場合p0というのがBoxになっているPointです。

Boxにラップした構造体には普通にドット演算子でアクセスできます。
↑の場合、p0.xp0.yにアクセスしています。
こうすると初期化された構造体のメンバにアクセスできます。

変更可能(ミュータブル)なBox

変数の定義にmutを付けると、変更可能なBoxを定義できます。

    // 変更可能(ミュータブル)なBox  
    let mut p1: Box<Point> = Box::new(Point {  
        x: 1,  
        y: 2,  
    });  

    p1.x += 10;  
    p1.y += 10;  

    println!("{} {}", p1.x, p1.y);  // 11 12  

↑の例ではPointをBoxでラップしていますが、mutにしているのでメンバ変数を途中で変えることができます。
↑ではp1.x += 10;とやってメンバx10を加算、p1.y += 10;でメンバy10を加算しています。

別のBoxへの代入

Boxは別のBoxに代入(ムーブ)可能です。

    // 別のBoxへの代入  
    let p2: Box<Point> = Box::new(Point {  
        x: 1,  
        y: 2,  
    });  
    let p3: Box<Point> = p2;  // ムーブ  

    // println!("{} {}", p2.x, p2.y);  // p2はムーブされているので使えない  
    println!("{} {}", p3.x, p3.y);  // 1 2  

ムーブされた元のBoxの変数は使用できなくなります。
この辺はRustの通常のふるまいです。

使い道がなさそうなi32のBox

Boxにi32の型を指定するとi32をヒープに確保できます。

    // i32とBox  
    let i: Box<i32> = Box::new(123);  

    println!("{}", i);  // 123  

確保はできますが、使い道があるかどうかは謎です。

配列とBox

配列とBoxを組み合わせることもできます。

    // 配列とBox  
    let ary: Box<[i32; 3]> = Box::new([1, 2, 3]);  

    for el in ary.iter() {  
        println!("{}", el);  
        // 1  
        // 2  
        // 3  
    }  

この場合、配列はヒープにメモリが確保されます。
しかし配列は固定長なので、ヒープにメモリを確保するメリットはあまり無いかもしれません。
i32と同じですね。

StringとBox

StringとBoxを組み合わせることも可能です。

    // StringとBox  
    // Stringはデフォルトで可変長になっているためヒープにする必要はあまりない  
    let mut s0: Box<String> = Box::new(String::from("abc"));  
    let mut s1: String = String::from("def");  

    s0.push_str("123");  
    s1.push_str("123");  

    println!("{}", s0);  // abc123  
    println!("{}", s1);  // def123  

しかしStringはデフォルトで可変長文字列になってます。
そのためヒープを内蔵しているようなものです。
ですのでBoxでわざわざヒープにする必要はないかもしれません。

Boxの使い道

やはりこうして見ると、Boxが輝くのは自作の構造体をヒープに確保する時だと言えます。
スタックではなくヒープにメモリを確保して、巨大なバイトを扱いたい場合はBoxを使うのが向いていると言えます。

VecやStringなどは可変長になっていてヒープが内蔵されているようなものです。
そのためこれらのオブジェクトとBoxを組み合わせるのはあまり意味がないことかもしれません。

おわりに

今回はRustのBoxについて解説しました。
ヒープからメモリを取ってきたい、となったらBoxを使ってみましょう。

🦝 < Boxは箱という意味だよ

🐭 < 箱入り娘