【Rust】構造体を理解する
Rsutの構造体(Struct)は複数の関連する値をひとつのデータ型を名前をつけて独自のデータ型として扱うための仕組みです。
この記事では、構造体の使い方、種類、設計のポイントについて解説します。
構造体とは
構造体(Struct)は複数の関連する値をひとつのデータ型を名前をつけて独自のデータ型として管理する仕組みです。他のプログラミング言語の「クラス」に似ていますが、Rustの構造体は単なるデータの集まりを表すだけであり、メソッドを直接持つことはできません。しかし、impl を使うことで、構造体に関連するメソッドを定義できます。
構造体の種類
Rustでは3種類の構造体が用意されています。
- 名前付きフィールド構造体
- タプル構造体
- ユニット構造体
名前付きフィールド構造体(Named Struct)
名前付きフィールド構造体は、最も一般的な構造体で、各フィールドに名前をつけてデータを管理できます。
例えば、RPGのキャラクター情報を表す際に、以下のような構造体を持つことができます。
struct Character {
name: String, // キャラクター名
char_type: String, // キャラクターの属性
level: u8, // レベル
hp: u8, // HP
attack: u8, // 攻撃力
defense: u8, // 防御力
}
fn main() {
let flame_dragon = Character {
name: String::from("フレイムドラゴン"),
char_type: String::from("炎"),
level: 25,
hp: 90,
attack: 100,
defense: 75,
};
println!("CharacterName: {}", flame_dragon.name);
println!("CharacterType: {}", flame_dragon.char_type);
}
タプル構造体(Tuple Struct)
タプル構造体はフィールド名を持たず、データの順番だけで管理する構造体です。そのため、名前をつける必要がないシンプルなデータ構造(例:座標やRGBデータ)に適しています。
例えば、RGBカラー情報を表す際に、以下のような構造体を持つことができます。
struct RGB(u8, u8, u8);
fn main() {
let orange = RGB(255, 165, 0);
println!("RGB({}, {}, {})", orange.0, orange.1, orange.2);
}
ユニット構造体(Unit-like Struct)
ユニット構造体は、フィールドを持たない特別な構造体です。
これは、特定の型であることを示したり、トレイトを実装するためのマーカーとして使われたりします。
struct Marker;
fn main() {
let _m = Marker;
}
構造体とメソッド
Rustの構造体自体にはメソッドを持たせることはできませんが、impl
キーワードを使うことで、関連するメソッドを追加できます。また、Rustのメソッドは通常の関数に似ていますが、第一引数に self
を指定することで、構造体のデータにアクセスできます。
impl
を使ったメソッドの追加
メソッドの基本形として、Character構造体に、 greet というメソッドを追加します。
ポイント
- impl Character の中で fn greet(&self) を定義している。
- &self を第一引数に取ることで、このメソッドは Character のインスタンスを参照できる。
- self.name を使って構造体のデータを取得できる。
struct Character {
name: String,
}
impl Character {
fn greet(&self) {
println!("こんにちは!私は{}です。", self.name);
}
}
fn main() {
let alice = Character {
name: String::from("Alice"),
};
alice.greet();
}
上記のように、Character構造体に対して、impl
キーワードを使って Character構造体に関連するメソッドを定義しました。
self, &self, &mut selfの違い
Rustのメソッドでは、第一引数に self を指定しますが、以下の3種類の方法があります。
種類 | 説明 | 用途 |
---|---|---|
self |
所有権を消費する(このインスタンスは使えなくなる) | - 新しいインスタンスを作成する |
&self |
共有参照(読み取り専用でデータを取得できる) | - インスタンスの情報を表示する |
&mut self |
可変参照(データを変更できる) | - インスタンスのフィールドを変更する |
new
によるインスタンス生成
Rustでは new
を定義することで、構造体のインスタンスを簡単に作成できます。Javaなどの
コンストラクタ的な役割を果たします。
struct Character {
name: String,
}
impl Character {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
fn greet(&self) {
println!("こんにちは!私は{}です。", self.name);
}
}
fn main() {
let alice = Character::new("Alice");
alice.greet();
}
関連関数
impl
ブロック内に self
を引数に取らない関数を定義することもできます。 これは、構造体に関連付けられているので、関連関数と呼ばれます。関連関数は関数であり、対象となる構造体のインスタンスが存在しないため、メソッドではありません。
上記の解説で、new
について取り上げましたが、これも関連関数です。
実践! Character構造体を使った例
use rand::{Rng, rng};
struct Character {
name: String, // キャラクター名
char_type: String, // キャラクターの属性
level: u8, // レベル
hp: u8, // HP
attack: u8, // 攻撃力
defense: u8, // 防御力
}
impl Character {
fn new(name: &str, char_type: &str, level: u8, hp: u8, attack: u8, defense: u8) -> Self {
Self {
name: name.to_string(),
char_type: char_type.to_string(),
level,
hp,
attack,
defense,
}
}
// キャラクターの情報を表示する
fn show_status(&self) {
println!(
"{} (属性: {}) - Lv:{} | HP:{} | 攻撃力:{} | 防御力:{}",
self.name, self.char_type, self.level, self.hp, self.attack, self.defense
);
}
// 攻撃アクション(相手のHPを減らす)
fn attack_enemy(&self, enemy: &mut Character) {
let mut rng = rng();
let random_factor: f64 = rng.random_range(0.85..=1.05); // 乱数(0.85 ~ 1.15)
let base_damage = (((10.0 * (self.level as f64) + 20.0) * (self.attack as f64) / (enemy.defense as f64)) / 20.0 + 2.0) * random_factor;
let damage = base_damage.floor() as u8;
enemy.hp = enemy.hp.saturating_sub(damage);
println!("⚔️ {} が {} に {} ダメージを与えた!", self.name, enemy.name, damage);
}
// 回復アクション(HPを回復する)
fn heal(&mut self, amount: u8) {
self.hp += amount;
println!("{} は {} 回復した! (HP: {})", self.name, amount, self.hp);
}
}
fn main() {
let mut flame_dragon = Character::new(
"フレイムドラゴン",
"炎",
25,
90,
100,
75,
);
let mut rock_dragon = Character::new(
"ロックドラゴン",
"岩",
20,
85,
90,
100,
);
flame_dragon.show_status();
rock_dragon.show_status();
println!("\n----------------------------\n");
flame_dragon.attack_enemy(&mut rock_dragon);
rock_dragon.attack_enemy(&mut flame_dragon);
println!("\n----------------------------\n");
flame_dragon.attack_enemy(&mut rock_dragon);
rock_dragon.attack_enemy(&mut flame_dragon);
println!("\n----------------------------\n");
flame_dragon.attack_enemy(&mut rock_dragon);
rock_dragon.attack_enemy(&mut flame_dragon);
println!("\n----------------------------\n");
flame_dragon.show_status();
rock_dragon.show_status();
println!("\n----------------------------\n");
flame_dragon.heal(10);
println!("\n----------------------------\n");
flame_dragon.show_status();
rock_dragon.show_status();
}
フレイムドラゴン (属性: 炎) - Lv:25 | HP:90 | 攻撃力:100 | 防御力:75
ロックドラゴン (属性: 岩) - Lv:20 | HP:85 | 攻撃力:90 | 防御力:100
----------------------------
⚔️ フレイムドラゴン が ロックドラゴン に 13 ダメージを与えた!
⚔️ ロックドラゴン が フレイムドラゴン に 14 ダメージを与えた!
----------------------------
⚔️ フレイムドラゴン が ロックドラゴン に 14 ダメージを与えた!
⚔️ ロックドラゴン が フレイムドラゴン に 14 ダメージを与えた!
----------------------------
⚔️ フレイムドラゴン が ロックドラゴン に 14 ダメージを与えた!
⚔️ ロックドラゴン が フレイムドラゴン に 15 ダメージを与えた!
----------------------------
フレイムドラゴン (属性: 炎) - Lv:25 | HP:47 | 攻撃力:100 | 防御力:75
ロックドラゴン (属性: 岩) - Lv:20 | HP:44 | 攻撃力:90 | 防御力:100
----------------------------
フレイムドラゴン は 10 回復した! (HP: 57)
----------------------------
フレイムドラゴン (属性: 炎) - Lv:25 | HP:57 | 攻撃力:100 | 防御力:75
ロックドラゴン (属性: 岩) - Lv:20 | HP:44 | 攻撃力:90 | 防御力:100
まとめ
- Rustの構造体はデータをまとめるための仕組みで、impl を使うことでメソッドを追加できる
- self の3つの種類(self, &self, &mut self)を理解すると、適切なメソッド設計ができる
- new メソッドを定義すると、インスタンスの作成が簡単になる
Discussion