Rustは、メモリ安全性と高い実行速度を両立したシステムプログラミング言語として、2026年現在も多くの開発現場で中心的な役割を担っています。
Rustにおけるプログラミングの根幹を支える要素の一つが、関連するデータを一つのまとまりとして扱う「構造体(struct)」です。
オブジェクト指向言語における「クラス」に近い役割を果たしますが、Rustの構造体はデータと振る舞いを明確に分離して定義する点が特徴です。
本記事では、Rustの構造体の基本的な定義方法から、メソッドの実装、さらには継承が存在しないRustにおける設計手法までを詳しく解説します。
Rustにおける構造体の基本概念
構造体は、異なるデータ型の値を組み合わせて、独自のデータ型を作成するための仕組みです。
例えば、ユーザー情報を扱う際に「名前」「メールアドレス」「年齢」といった個別の変数をバラバラに管理するのは非効率的です。
構造体を使用することで、これらを「User」という一つのエンティティとしてカプセル化し、コードの可読性と保守性を飛躍的に向上させることができます。
Rustの構造体には、用途に合わせて使い分けることができる3つの異なる形式が存在します。
1つ目は、フィールド名を明示する「名前付きフィールド構造体」です。
2つ目は、フィールド名を省略し、データの順序に意味を持たせる「タプル構造体」です。
3つ目は、データを一切持たない「ユニット構造体」です。
開発者はこれらの形式を適切に選択することで、メモリ効率と表現力のバランスを取ることができます。
名前付きフィールド構造体の定義と利用
最も一般的で利用頻度が高いのが、各データに名前を付けて管理する形式です。
以下のコードは、典型的な構造体の定義とインスタンス化の例を示しています。
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
fn main() {
// インスタンスの作成
let user1 = User {
email: String::from("example@test.com"),
username: String::from("rust_user"),
active: true,
sign_in_count: 1,
};
println!("Username: {}", user1.username);
}
Username: rust_user
構造体を定義する際は、structキーワードに続けて大文字で始まる型名を記述します。
各フィールドはフィールド名: 型名の形式で定義し、最後にカンマを記述するのが慣習です。
インスタンスを作成する際は、定義したすべてのフィールドに対して値を割り当てる必要があります。
一部のフィールドだけを初期化することはできず、すべてのデータが揃っていることがコンパイラによって保証されます。
タプル構造体による簡潔なデータ表現
フィールドに名前を付ける必要がなく、データの型とその順序だけで意味が通じる場合にはタプル構造体が適しています。
例えば、3次元空間の座標(x, y, z)や、色のRGB値などが代表的な例です。
struct Color(i32, i32, i32);
struct Point(f64, f64, f64);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0.0, 0.0, 0.0);
println!("Color RGB: ({}, {}, {})", black.0, black.1, black.2);
}
Color RGB: (0, 0, 0)
タプル構造体の各要素にアクセスするには、ドット記法の後にインデックス(0, 1, 2…)を指定します。
たとえ内部の型構成が同じであっても、異なる名前のタプル構造体は別個の型として扱われるため、型安全性を高めることができます。
ユニット構造体の活用シーン
フィールドを全く持たない構造体は、ユニット構造体と呼ばれます。
一見すると使い道がないように思えますが、主に「特定のトレイトを実装するが、データ自体は保持しない」場合に利用されます。
例えば、アルゴリズムの挙動を切り替えるためのマーカーとして使用することが一般的です。
struct AlwaysEqual;
fn main() {
let _subject = AlwaysEqual;
}
ユニット構造体は、ゼロコスト抽象化を推進するRustにおいて、コンパイル時のチェックに役立つ強力なツールとなります。
構造体の操作と便利な構文
Rustには、構造体をより効率的に操作するための便利なシンタックスシュガーが用意されています。
フィールド初期化の略記法(Field Init Shorthand)
変数名とフィールド名が同一である場合、記述を省略してシンプルに書くことができます。
fn build_user(email: String, username: String) -> User {
User {
email, // email: email の略記
username, // username: username の略記
active: true,
sign_in_count: 1,
}
}
この機能により、関数の引数から構造体を生成する際の冗長なコードを削減できます。
構造体更新記法(Struct Update Syntax)
既存のインスタンスの一部の値を変更し、残りの値を引き継いだ新しいインスタンスを作成したい場合があります。
その際、..記法を使用することで、残りのフィールドをコピーまたはムーブできます。
let user2 = User {
email: String::from("another@test.com"),
..user1
};
ここで注意すべき点は、所有権の移動です。
String型のようにCopyトレイトを実装していないデータが含まれる場合、..user1によってuser1の一部のデータがuser2に移動し、元の変数は使用できなくなることがあります。
この挙動は、Rustのメモリ管理ルールに基づいているため、意図しないコンパイルエラーを防ぐために意識しておく必要があります。
メソッドの実装:implブロック
構造体そのものはデータのみを保持しますが、impl(implementation)ブロックを使用することで、そのデータに関連する関数(メソッド)を定義できます。
これにより、データと操作を論理的に結びつけることが可能になります。
メソッドの定義とself
メソッドの第1引数には、必ずself(またはその参照)を取ります。
これは、そのメソッドが構造体のインスタンス上で呼び出されることを意味します。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// 面積を計算するメソッド
fn area(&self) -> u32 {
self.width * self.height
}
// 幅を拡張するメソッド(可変参照)
fn grow(&mut self, factor: u32) {
self.width *= factor;
}
}
fn main() {
let mut rect = Rectangle { width: 30, height: 50 };
println!("Area: {}", rect.area());
rect.grow(2);
println!("New width: {}", rect.width);
}
Area: 1500
New width: 60
多くのメソッドでは、所有権を奪わずにデータを読み取るため、&self(不変参照)を使用します。
インスタンスの値を変更する必要がある場合は、&mut self(可変参照)を引数に取ります。
稀にself(所有権の移動)を取ることもありますが、これは主にインスタンスを別の型に変換する場合などに限定されます。
関連関数(Associated Functions)
implブロック内には、selfを引数に取らない関数を定義することもできます。
これらは「関連関数」と呼ばれ、他言語における「静的メソッド」に近い挙動を示します。
代表的な例は、新しいインスタンスを生成して返すnew関数です。
impl Rectangle {
// 正方形を作成する関連関数
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let sq = Rectangle::square(10);
println!("Square area: {}", sq.area());
}
関連関数を呼び出す際は、インスタンスではなく型名に対してダブルコロン::を使用します。
構造体と所有権の深い関係
Rustの構造体において最も重要な考慮事項の一つが、フィールドが持つデータの所有権です。
構造体がStringのような所有型を持つ場合、その構造体のインスタンスが破棄されるときに、フィールドのデータも自動的にメモリから解放されます。
一方で、構造体に参照(&strなど)を持たせたい場合は、「ライフタイム記法」が必要になります。
struct ImportantExcerpt<'a> {
part: &'a str,
}
ライフタイム記法<'a>は、構造体が参照しているデータの寿命が、構造体自身の寿命よりも長いことをコンパイラに伝えるためのものです。
初心者のうちは、構造体のフィールドには所有権を持つ型(StringやVecなど)を使用するのが無難です。
参照をフィールドに持つ設計は高度な最適化が必要な場面まで避け、まずは所有権をベースにした設計を心がけましょう。
継承の代替案:コンポジションとトレイト
JavaやC++のようなオブジェクト指向言語に慣れている開発者は、Rustにクラスの継承(Inheritance)がないことに驚くかもしれません。
Rustでは「継承によるコードの共有」ではなく、「コンポジション(合成)」と「トレイト」による拡張が推奨されています。
コンポジション(Composition)
ある構造体の機能を別の構造体で利用したい場合、その構造体をフィールドとして持たせる手法がコンポジションです。
struct Engine {
horsepower: u32,
}
struct Car {
brand: String,
engine: Engine, // 構造体を内包する
}
これにより、継承に伴う深いクラス階層の複雑さを回避しつつ、柔軟な部品の組み合わせが可能になります。
トレイトによる振る舞いの定義
異なる構造体に対して共通の振る舞いを定義したい場合は、traitを使用します。
これはインターフェースに近い概念であり、多態性(ポリモーフィズム)を実現するための仕組みです。
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("記事タイトル: {}", self.title)
}
}
トレイトを使用することで、異なる型に対して一貫したインターフェースを提供でき、コードの再利用性が高まります。
構造体のメモリレイアウトとパフォーマンス
Rustの構造体は、デフォルトでC言語の構造体に近いメモリレイアウトを持ちます。
しかし、Rustコンパイラはメモリ効率を最適化するために、フィールドの順序を自動的に並べ替える(Reordering)ことがあります。
これにより、データ構造の間に発生するパディング(隙間)を最小限に抑え、キャッシュ効率を向上させることができます。
低レイヤーのプログラミングやFFI(他言語連携)を行う際に、特定のレイアウトを強制したい場合は、#[repr(C)]属性を使用します。
#[repr(C)]
struct HardwareRegister {
address: u32,
value: u8,
}
通常のアプリケーション開発ではコンパイラの最適化に任せるのが最善ですが、このような微細な制御が可能である点もRustの魅力です。
デバッグを容易にするDeriveトレイト
構造体の中身を表示してデバッグしたい場合、自前で表示用関数を書く必要はありません。
#[derive(Debug)]アノテーションを付与することで、標準ライブラリが提供するデバッグ表示機能を自動で実装できます。
#[derive(Debug)]
struct Player {
name: String,
score: u32,
}
fn main() {
let player = Player {
name: String::from("Hero"),
score: 100,
};
// {:?} または {:#?} でデバッグ出力可能
println!("{:?}", player);
}
Player { name: "Hero", score: 100 }
このほかにも、Clone(値のコピーを可能にする)やPartialEq(比較を可能にする)などの便利なトレイトを簡単に導入できます。
これにより、ボイラープレートコードを大幅に削減し、本質的なロジックの実装に集中できます。
まとめ
Rustの構造体は、単なるデータのコンテナを超えて、型安全性とメモリ管理、そしてプログラムの設計思想を具現化するための重要な要素です。
本記事では、以下のポイントを中心に解説しました。
- 名前付き、タプル、ユニットという3つの構造体形式の使い分け
implブロックによるメソッドと関連関数の定義- 所有権に基づいたフィールド設計と、ライフタイムの基礎
- 継承に頼らない「コンポジション」と「トレイト」による柔軟な設計
deriveによる開発効率の向上
構造体をマスターすることは、Rust特有の「所有権」や「トレイト」といった概念を使いこなすための第一歩です。
まずはシンプルなデータ構造から始め、徐々にメソッドやトレイトを組み合わせることで、堅牢で効率的なRustアプリケーションを構築できるようになるでしょう。
2026年のモダンな開発環境において、Rustの構造体を適切に使いこなし、安全で高速なソフトウェア開発を目指してください。
