今回はRustの構造体(struct)について詳しく解説していきます。
プログラムを書いていると、「いくつかのデータをセットにして扱いたいな」と思う瞬間、結構ありますよね?
例えば、ゲームのキャラクターなら名前、HP、MPとか。ユーザー情報なら、ID、名前、メールアドレスとか。そんなふうに、関連するデータたちを一つにまとめて、自分だけのオリジナルの「型」として定義できるのが、構造体なんです!
構造体は、いわばデータの「設計図」。この設計図をもとに、具体的なデータの実体(インスタンスって言います)を作っていくイメージです。
この記事で学べること
- Rustの構造体の基本的な考え方
- 構造体の3つの種類の定義方法
- 構造体のインスタンス(実体)の作り方
- 構造体のデータにアクセスしたり、変更したりする方法
- 構造体に「メソッド」っていう便利な機能を追加する方法
- 構造体を使う上でのちょっとしたコツ
Rustの構造体の基本
まずは、構造体の「設計図」を作る方法(定義)と、その設計図から「実物」を作る方法(インスタンス化)を見ていきましょう。Rustには、主に3種類の構造体があります。
3種類の構造体 - 名前付きフィールド、タプル、ユニットライク
一番よく使うのが「名前付きフィールド構造体」。それぞれのデータに名前(フィールド名)を付けて管理できる、とても分かりやすい形式です。
名前付きフィールド構造体の定義
// Userという名前の構造体を定義 struct User { username: String, // ユーザー名(文字列) email: String, // メールアドレス(文字列) sign_in_count: u64, // サインイン回数(64ビット符号なし整数) active: bool, // アクティブかどうか(真偽値) } // Pointという名前の構造体を定義(2次元座標) struct Point { x: f64, // x座標(64ビット浮動小数点数) y: f64, // y座標(64ビット浮動小数点数) }
こんな風に、`struct 構造体名 { フィールド名: データ型, ... }` という形で書きます。フィールド名を指定するので、どのデータが何を表しているか一目瞭然なのが良い点ですね!
次に「タプル構造体」。これはフィールド名がなく、データ型だけを指定するシンプルな形式です。個々のデータに名前を付けるほどでもないけど、ひとまとめにしたい場合に便利です。
タプル構造体の定義
// Colorという名前のタプル構造体(RGB値) struct Color(u8, u8, u8); // (赤, 緑, 青) - 各色は8ビット符号なし整数 // Coordinatesという名前のタプル構造体(3次元座標) struct Coordinates(f64, f64, f64); // (x, y, z) - 各座標は64ビット浮動小数点数
書き方は `struct 構造体名(データ型, ...);` です。シンプルですね!
最後に「ユニットライク構造体」。これはフィールドを全く持たない構造体です。特定の機能(トレイトっていう、ちょっと高度な機能)を実装するためだけに存在したり、マーカーとして使われたりします。最初はあまり出番がないかもしれませんが、こういうのもあるんだな、と頭の片隅に置いておくと良いでしょう。
ユニットライク構造体の定義
// AlwaysEqualという名前のユニットライク構造体 struct AlwaysEqual;
`struct 構造体名;` と書くだけです。
構造体のインスタンスを作成・初期化する方法
設計図(定義)ができたら、次は実体(インスタンス)を作りましょう!
名前付きフィールド構造体のインスタンス化
`User` 構造体のインスタンスを作ってみます。`構造体名 { フィールド名: 値, ... }` の形で書きます。
// 上で定義したUser構造体 struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() { // user1という名前でUserインスタンスを作成 let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; // フィールドの順番は定義時と同じじゃなくてもOK let user2 = User { active: false, username: String::from("anotheruser"), email: String::from("another@example.com"), sign_in_count: 5, }; // 変数名とフィールド名が同じなら省略も可能(フィールド初期化省略記法) let username = String::from("shortcut_user"); let email = String::from("shortcut@example.com"); let user3 = User { username, // username: username, と書くのと同じ email, // email: email, と書くのと同じ active: true, sign_in_count: 10, }; }
`String::from("...")` は文字列データを作るためのお作法だと思ってください。
フィールドの初期化は、定義したすべてのフィールドに対して行う必要がある点に注意してくださいね。値をセットし忘れるとコンパイルエラーになります。
タプル構造体のインスタンス化
タプル構造体の場合は、`構造体名(値, ...)` の形で書きます。
// 上で定義したColor構造体 struct Color(u8, u8, u8); fn main() { // 黒色を表すインスタンスを作成 let black = Color(0, 0, 0); // 白色を表すインスタンスを作成 let white = Color(255, 255, 255); }
定義した順番通りに値を指定します。シンプルで書きやすいですね!
Rust構造体のデータ操作 - フィールドへのアクセスと変更
インスタンスを作ったら、その中身のデータ(フィールド)を使ったり、変更したりしたくなりますよね。その方法を見ていきましょう。
フィールドへのアクセス
名前付きフィールド構造体の場合、ドット (`.`) を使ってアクセスします。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() { let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; // user1のemailフィールドにアクセスして表示 println!("Email: {}", user1.email); // 出力: Email: someone@example.com // user1のactiveフィールドにアクセス if user1.active { println!("User is active!"); // 出力: User is active! } }
`インスタンス名.フィールド名` で、そのフィールドの値を取り出せます。とっても直感的!
タプル構造体の場合は、ドット (`.`) とインデックス番号(0から始まる)でアクセスします。
struct Color(u8, u8, u8); fn main() { let black = Color(0, 0, 0); // blackの最初の要素(赤)にアクセス println!("Red value: {}", black.0); // 出力: Red value: 0 // blackの3番目の要素(青)にアクセス println!("Blue value: {}", black.2); // 出力: Blue value: 0 }
`インスタンス名.インデックス番号` でアクセスできます。
フィールドの変更
フィールドの値を変更するには、インスタンスが「ミュータブル(変更可能)」である必要があります。インスタンスを作成するときに `let mut` を使います。
struct User { username: String, email: String, sign_in_count: u64, active: bool, } fn main() { // 'mut' をつけて、変更可能なインスタンスとして作成 let mut user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; println!("Original email: {}", user1.email); // 出力: Original email: someone@example.com // emailフィールドの値を変更 user1.email = String::from("new_email@example.com"); println!("New email: {}", user1.email); // 出力: New email: new_email@example.com // sign_in_countを増やす user1.sign_in_count += 1; println!("Sign in count: {}", user1.sign_in_count); // 出力: Sign in count: 2 }
`let user1 = ...` の代わりに `let mut user1 = ...` と書くことで、`user1` のフィールド値を後から変更できるようになります。
Rustは安全性を重視するので、デフォルトでは変更不可(イミュータブル)なんです。変更したい場合は、`mut` を付けて「変更しますよ」と意思表示する必要があるんですね。
構造体をより強力に - メソッドの定義と使い方
構造体はデータをまとめるだけじゃありません。そのデータに関連する「操作」や「処理」も一緒にまとめておくことができるんです!それが「メソッド」です。
例えば、`User` 構造体に「ユーザー情報を要約して表示する」機能を追加したり、`Rectangle`(長方形)構造体に「面積を計算する」機能を追加したりできます。こうすることで、データとそのデータに対する操作がセットになり、コードがより整理され、オブジェクト指向プログラミングのような考え方で書けるようになります。
`impl`ブロックでメソッドを実装する
メソッドは `impl` (implementation = 実装) というキーワードを使って定義します。`impl 構造体名 { ... }` の中に、関数(メソッド)を書いていく感じです。
長方形を表す構造体 `Rectangle` を例に見てみましょう。
// 長方形を表す構造体 struct Rectangle { width: u32, height: u32, } // Rectangle構造体のためのメソッドを実装するブロック impl Rectangle { // 面積を計算するメソッド `area` // `&self` は、Rectangleインスタンス自身を不変(変更不可)で借用することを意味する fn area(&self) -> u32 { // フィールドにアクセスして面積を計算 self.width * self.height } // 幅が0より大きいかチェックするメソッド `has_valid_width` fn has_valid_width(&self) -> bool { self.width > 0 } // 別のRectangleインスタンスを中に含めることができるか判定するメソッド `can_hold` // 他のRectangleインスタンスも引数として受け取る fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } // 関連関数(スタティックメソッドとも呼ばれる) `square` // `&self` を引数に取らない。インスタンスなくても呼び出せる。 // 正方形を作成するのに便利 fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size } } }
`impl Rectangle { ... }` の中に `fn` で関数を定義していますね。これがメソッドです。
メソッドの最初の引数に注目してください。`&self` や `&mut self`、あるいは `self` が来ることが多いです。
- `&self`: メソッドがインスタンスのデータを読み取るだけの場合に使います。インスタンス自身を「不変の参照」として受け取ります。一番よく使う形式です。上の `area`, `has_valid_width`, `can_hold` がこれにあたります。
- `&mut self`: メソッドがインスタンスのデータを変更する場合に使います。インスタンス自身を「可変の参照」として受け取ります。例えば、HPを回復するメソッドなら、HPフィールドを変更するので `&mut self` が必要です。
- `self`: メソッドがインスタンスの所有権を奪う場合に使います。あまり頻繁には使いません。
`&self` を取らない関数(例: `square`)は「関連関数」と呼ばれます。これはインスタンスがなくても `構造体名::関数名()` の形で呼び出せる特別な関数です。インスタンスを作るための便利なファクトリ関数としてよく使われます。
メソッドの呼び出しと`self`キーワード
定義したメソッドは、インスタンスからドット (`.`) を使って呼び出します。関数呼び出しと同じように `()` を付けます。
// 長方形を表す構造体 struct Rectangle { width: u32, height: u32, } // Rectangle構造体のためのメソッドを実装するブロック impl Rectangle { // 面積を計算するメソッド `area` // `&self` は、Rectangleインスタンス自身を不変(変更不可)で借用することを意味する fn area(&self) -> u32 { // フィールドにアクセスして面積を計算 self.width * self.height } // 幅が0より大きいかチェックするメソッド `has_valid_width` fn has_valid_width(&self) -> bool { self.width > 0 } // 別のRectangleインスタンスを中に含めることができるか判定するメソッド `can_hold` // 他のRectangleインスタンスも引数として受け取る fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } // 関連関数(スタティックメソッドとも呼ばれる) `square` // `&self` を引数に取らない。インスタンスなくても呼び出せる。 // 正方形を作成するのに便利 fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size } } }fn main() { // Rectangleインスタンスを作成 let rect1 = Rectangle { width: 30, height: 50 }; let rect2 = Rectangle { width: 10, height: 40 }; let rect3 = Rectangle { width: 60, height: 45 }; // `area` メソッドを呼び出し println!( "The area of the rectangle is {} square pixels.", rect1.area() // rect1インスタンスのareaメソッドを呼び出す ); // 出力: The area of the rectangle is 1500 square pixels. // `has_valid_width` メソッドを呼び出し if rect1.has_valid_width() { println!("rect1 has a valid width."); // 出力: rect1 has a valid width. } // `can_hold` メソッドを呼び出し println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); // 出力: Can rect1 hold rect2? true println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); // 出力: Can rect1 hold rect3? false // 関連関数 `square` を呼び出して正方形インスタンスを作成 // インスタンス名ではなく、構造体名から `::` で呼び出す let sq = Rectangle::square(25); println!("Created a square with width: {}", sq.width); // 出力: Created a square with width: 25 println!("Square area: {}", sq.area()); // 出力: Square area: 625 }
メソッド定義の際の `&self` や `&mut self` は、メソッド呼び出し時には自動的に処理されるので、`rect1.area()` のように書くだけでOKです。Rustが裏側でうまくやってくれます。
`self` はメソッド内で、そのメソッドを呼び出しているインスタンス自身を指す、特別なキーワードだと覚えておきましょう!
関連関数はインスタンスに紐付かないので、`Rectangle::square(25)` のように `構造体名::関数名()` という形で呼び出します。
Rustの構造体の具体的な使い方
では、ここまでの知識を総動員して、もう少し具体的なサンプルコードを見てみましょう!「本の情報」を管理する構造体 `Book` を作ってみます。
// 本の情報を格納する構造体 struct Book { title: String, author: String, page_count: u32, } // Book構造体のメソッドを実装 impl Book { // 本の情報を要約して返すメソッド fn summary(&self) -> String { // format!マクロを使って文字列を組み立てる format!( "Title: {}, Author: {}, Pages: {}", self.title, self.author, self.page_count ) } // 新しい本インスタンスを作成する関連関数(ファクトリ関数) fn new(title: &str, author: &str, page_count: u32) -> Book { Book { title: String::from(title), author: String::from(author), page_count, // フィールド初期化省略記法 } } } fn main() { // 関連関数 `new` を使ってBookインスタンスを作成 let book1 = Book::new("The Rust Programming Language", "Steve Klabnik and Carol Nichols", 568); let book2 = Book::new("Programming Rust", "Jim Blandy, Jason Orendorff, and Leonora F.S. Tindall", 648); // book1のフィールドにアクセス println!("Book 1 Title: {}", book1.title); // 出力: Book 1 Title: The Rust Programming Language // book2の `summary` メソッドを呼び出す let summary2 = book2.summary(); println!("Book 2 Summary: {}", summary2); // 出力: Book 2 Summary: Title: Programming Rust, Author: Jim Blandy, Jason Orendorff, and Leonora F.S. Tindall, Pages: 648 // ミュータブルなインスタンスを作成してページ数を変更 let mut book3 = Book::new("Another Book", "Author Name", 300); println!("Original page count for book3: {}", book3.page_count); // 出力: Original page count for book3: 300 book3.page_count = 350; // ページ数を変更 println!("Updated page count for book3: {}", book3.page_count); // 出力: Updated page count for book3: 350 println!("Updated summary for book3: {}", book3.summary()); // 出力: Updated summary for book3: Title: Another Book, Author: Author Name, Pages: 350 }
このコードでは、`Book` 構造体を定義し、`impl` ブロックで `summary` メソッド(情報を要約)と `new` 関連関数(インスタンス作成を簡単にする)を実装しました。
`main` 関数では、`new` 関数でインスタンスを作り、フィールドにアクセスしたり、`summary` メソッドを呼び出したりしています。また、`mut` を使ってフィールド値を変更する例も示しています。
どうでしょう?構造体の定義からメソッドの実装、そして実際の使い方まで、一連の流れが見えてきたのではないでしょうか?
注意点とベストプラクティス - Rust構造体を使いこなすために
最後に、Rustの構造体をよりスムーズに、そして「Rustらしく」使うための、いくつかのポイントやコツを紹介しますね。
命名規則に従おう
Rustコミュニティでは、一般的に次のような命名規則が使われています。これに従うと、他の人が書いたコードも読みやすくなりますし、自分のコードも理解されやすくなります。- 構造体名: アッパーキャメルケース (例: `UserProfile`, `Point`, `Rectangle`)
- フィールド名: スネークケース (例: `user_name`, `page_count`, `is_active`)
- メソッド名: スネークケース (例: `calculate_area`, `get_summary`)
`mut`の付け忘れに注意
フィールドの値を変更しようとしたときにエラーが出たら、まずインスタンス変数を `let mut` で宣言しているか確認しましょう。Rustのイミュータビリティ(不変性)は安全のための仕組みですが、慣れるまでは少し戸惑うかもしれません。デバッグ出力を簡単に: `#[derive(Debug)]`
構造体のインスタンスを `println!` などでサッと表示して中身を確認したい時、ありますよね。そんな時は、構造体定義の直前に `#[derive(Debug)]` と書いてみてください。#[derive(Debug)] // これを追加! struct Point { x: i32, y: i32, } fn main() { let p = Point { x: 10, y: 20 }; println!("Point details: {:?}", p); // 出力: Point details: Point { x: 10, y: 20 } }こうすると、`{:?}` というフォーマット指定子で、いい感じに中身を表示できるようになります。デバッグ時にめちゃくちゃ便利なので、ぜひ覚えておいてください!
所有権を意識しよう(少しだけ)
Rustの核心的な機能である「所有権」。構造体を関数に渡したり、関数から返したりする際に、この所有権がどう動くかを少しだけ意識すると、エラーを減らせます。基本的には、構造体インスタンスも他の値と同じように所有権が移動します。これらの点を頭に入れておくと、よりスムーズに構造体を扱えるようになるはずです!
【まとめ】
お疲れ様でした!この記事では、Rustのプログラミングで欠かせない「構造体」について、その基本から応用まで、たっぷりと解説してきました。
- 構造体は、関連するデータを一つにまとめるための設計図であること。
- 名前付きフィールド、タプル、ユニットライクという種類があること。
- `struct` で定義し、`{}` や `()` を使ってインスタンス化すること。
- ドット (`.`) でフィールドにアクセスし、`mut` があれば変更できること。
- `impl` ブロックを使って、構造体にメソッド(操作)を追加できること。
- `&self` でインスタンス自身を参照し、メソッド内でフィールドを使えること。
これらの知識があれば、もうあなたはRustの構造体を自信を持って使えるはずです!データを分かりやすく整理し、それに対する操作をまとめることで、よりクリーンで効率的なコードを書く第一歩を踏み出せましたね。
構造体をマスターしたら、次は「Enum(列挙型)」でさらに表現力を高めたり、「トレイト」で共通の振る舞いを定義したり、といったステップに進んでいくのがおすすめです。Rustの世界は奥が深いですが、一つひとつ着実に学んでいけば、必ず使いこなせるようになります。
0 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。