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.x
とp0.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;
とやってメンバx
に10
を加算、p1.y += 10;
でメンバy
に10
を加算しています。
別の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は箱という意味だよ
🐭 < 箱入り娘