関連するデータをまとめて、独自の型を作成しよう!
Rustプログラミングの世界へようこそ!😊 これまでのステップで、変数、データ型、制御構文、所有権といった基本的な概念を学んできました。今回は、より複雑なデータ構造を扱うための強力な機能、「構造体(struct)」とその振る舞いを定義する「実装(impl)」について学びます。
構造体を使うことで、関連する複数の値をひとまとめにして、意味のある独自のデータ型を作成できます。例えば、「ユーザー」を表す型を定義して、その中にID、名前、メールアドレスなどの情報を持たせることができます。
構造体(struct)の定義
構造体は struct
キーワードを使って定義します。いくつかの形式があります。
フィールドを持つ構造体
最も一般的な形式で、各フィールドに名前と型を指定します。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
// タプル構造体 (フィールド名がない)
struct Color(i32, i32, i32); // RGB値など
struct Point(i32, i32, i32); // 3次元座標など
// ユニット様構造体 (フィールドがない)
// トレイトを実装したいが、データは保持する必要がない場合に有用
struct AlwaysEqual;
User
構造体は、ユーザーの状態、名前、メールアドレス、サインイン回数という4つのフィールドを持っています。Color
や Point
はタプル構造体と呼ばれ、フィールド名を持ちません。AlwaysEqual
はユニット様構造体で、フィールドを全く持ちません。
構造体のインスタンス化
定義した構造体を使うには、その「インスタンス」を作成します。インスタンス化するには、構造体名を指定し、波括弧 {}
の中に各フィールド名と値を key: value
ペアで記述します。
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
// User構造体のインスタンスを作成
let mut user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
// フィールドの値にアクセス (ドット記法)
println!("Email: {}", user1.email); // Output: Email: someone@example.com
// フィールドの値を変更 (インスタンスが `mut` である必要あり)
user1.email = String::from("anotheremail@example.com");
println!("New Email: {}", user1.email); // Output: New Email: anotheremail@example.com
// インスタンス生成関数 (後述の `impl` で定義) を使う例
let user2 = build_user(String::from("user2@example.com"), String::from("user2"));
println!("User2 Username: {}", user2.username);
// 構造体更新構文 (`..`)
// user1 の値を使って新しい User インスタンスを生成
let user3 = User {
email: String::from("user3@example.com"),
..user1 // user1 の残りのフィールド値をコピー (active, sign_in_count)
// username は user1 の所有権が move されることに注意 (String のため)
// active や sign_in_count は Copy トレイトを実装しているのでコピーされる
};
// println!("{}", user1.username); // エラー: username の所有権は user3 に move された
println!("User3 Active: {}", user3.active);
// タプル構造体のインスタンス化
struct Color(i32, i32, i32);
let black = Color(0, 0, 0);
println!("Black color: R={}, G={}, B={}", black.0, black.1, black.2);
// ユニット様構造体のインスタンス化
struct AlwaysEqual;
let subject = AlwaysEqual;
// subject には特にアクセスできるデータはない
}
// Userインスタンスを生成するヘルパー関数
fn build_user(email: String, username: String) -> User {
// フィールド名と変数名が同じ場合は省略可能 (フィールド初期化省略記法)
User {
email, // email: email, と同じ
username, // username: username, と同じ
active: true,
sign_in_count: 1,
}
}
💡 フィールド初期化省略記法
関数の引数名やローカル変数名が、構造体のフィールド名と同じ場合、field: variable
の代わりに単に field
と書くことができます。これはコードを簡潔にするのに役立ちます。
⚠️ 所有権と構造体更新構文
構造体更新構文 ..
を使う際、コピーされるフィールドの型に注意が必要です。String
のような所有権を持つ型の場合、値はムーブ(移動)されます。bool
や数値型のように Copy
トレイトを実装している型の場合、値はコピーされます。ムーブが発生すると、元のインスタンスのそのフィールドにはアクセスできなくなります。
メソッドの定義(impl)
構造体を定義した後、その構造体に関連する関数を定義したくなることがよくあります。例えば、長方形を表す構造体に対して、その面積を計算する関数などです。このような関数を「メソッド」と呼び、impl
(implementation、実装)ブロックを使って定義します。
#[derive(Debug)] // デバッグ出力 {:?} を有効にする
struct Rectangle {
width: u32,
height: u32,
}
// Rectangle 構造体のための impl ブロック
impl Rectangle {
// メソッド: 第一引数が `self`, `&self`, または `&mut self`
// `&self` はインスタンスの不変の参照を受け取る (所有権は奪わない)
fn area(&self) -> u32 {
self.width * self.height
}
// 別の Rectangle インスタンスを受け取るメソッド
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// 関連関数 (Associated Function): 第一引数が `self` ではない
// よくコンストラクタとして使われる (インスタンスを生成する関数)
// `Self` は `impl` している型 (この場合は Rectangle) を指すエイリアス
fn square(size: u32) -> Self { // Self == Rectangle
Self { width: size, height: size }
}
// 可変の参照 `&mut self` を受け取るメソッド
// インスタンスの状態を変更する場合に使う
fn set_width(&mut self, width: u32) {
self.width = width;
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
let rect3 = Rectangle { width: 60, height: 45 };
let mut rect4 = Rectangle { width: 10, height: 10 }; // 変更するので mut
// メソッド呼び出し (`.` 演算子)
println!(
"The area of the rectangle {:?} is {} square pixels.",
rect1,
rect1.area() // rect1.area(&rect1) とほぼ同じだが、自動参照/脱参照が働く
);
println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); // true
println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); // false
// 関連関数の呼び出し (`::` 演算子)
let sq = Rectangle::square(25);
println!("Created a square: {:?}", sq);
// 可変メソッドの呼び出し
println!("rect4 width before: {}", rect4.width);
rect4.set_width(20); // rect4.set_width(&mut rect4) とほぼ同じ
println!("rect4 width after: {}", rect4.width);
}
メソッドと関連関数
impl
ブロック内で定義される関数には、大きく分けて2種類あります。
種類 | 第一引数 | 呼び出し方 | 説明 |
---|---|---|---|
メソッド (Method) | self , &self , または &mut self |
インスタンスに対して . 演算子 (例: instance.method() ) |
インスタンスのデータにアクセスしたり、変更したりします。self は所有権を奪い、&self は不変の参照、&mut self は可変の参照を受け取ります。 |
関連関数 (Associated Function) | self を取らない |
型に対して :: 演算子 (例: StructName::function() ) |
インスタンスを必要としない関数です。構造体の新しいインスタンスを返すコンストラクタ(初期化関数)としてよく使われます (例: String::from() , Rectangle::square() )。 |
🔗 自動参照・脱参照
メソッド呼び出し時 (.
演算子)、Rustは自動的に &
, &mut
, または *
を追加してくれるため、object.something()
は (&object).something()
や (&mut object).something()
のように、メソッド定義に合う形で呼び出されます。これにより、明示的な参照・脱参照の記述が減り、コードが読みやすくなります。
複数の impl
ブロックを持つことも可能です。例えば、構造体の定義と基本的なメソッドを一つの impl
ブロックに書き、特定の機能に関連するメソッド群を別の impl
ブロックに分けることができます。
struct Point {
x: f64,
y: f64,
}
// 基本的な操作
impl Point {
fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
// 距離に関する操作
impl Point {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let p = Point::new(3.0, 4.0);
println!("Distance from origin: {}", p.distance_from_origin()); // Output: 5
}
まとめ
今回は、Rustにおける構造体 struct
とその実装 impl
について学びました。✨
- 構造体 (
struct
): 関連するデータをひとまとめにし、新しい型を定義する方法。フィールドを持つ構造体、タプル構造体、ユニット様構造体がある。 - インスタンス化: 定義した構造体から具体的な値(インスタンス)を作成すること。フィールド初期化省略記法や構造体更新構文が便利。
- 実装 (
impl
): 構造体に関連する関数(メソッドや関連関数)を定義するブロック。 - メソッド: インスタンスを操作するための関数 (
&self
,&mut self
を取る)。 - 関連関数: 型自体に関連付けられた関数(コンストラクタなど)。
構造体と実装は、データを構造化し、そのデータに対する操作をカプセル化するための基本的な要素です。これらを使いこなすことで、より整理され、再利用性の高いコードを書くことができます。
次のステップでは、構造体と似ていますが、複数の「バリアント(種類)」を持つことができる「列挙型(enum)」について学びます。お楽しみに!🚀
コメント