Rustの構造体
構造体(struct)は、関係のある値をまとめて、名前のついたひとつの型にしたものです。タプルが名前のないちょっとした束だとすれば、構造体はすべてのフィールドに名前をつけます。そのおかげで、構造体を使うコードはぐっと読みやすくなります。
構造体を定義して作る
Section titled “構造体を定義して作る”struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30, };
println!("{}は{}歳です", alice.name, alice.age);}実行結果:
Aliceは30歳ですタプルの ("Alice", 30) と比べてみてください。構造体なら、alice.name と alice.age を見ただけで意味がわかります。0番目が名前で1番目が年齢、といったことを覚えておく必要はありません。
構造体を可変にする
Section titled “構造体を可変にする”変数と同じように、構造体のインスタンスもデフォルトでは不変です。作ったあとでフィールドを変更したいなら、mut をつけます。
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Alice"), age: 30, };
alice.age += 1; println!("{}は{}歳になりました", alice.name, alice.age);}メソッドで振る舞いを追加する
Section titled “メソッドで振る舞いを追加する”impl ブロックを使うと、構造体に関数をくっつけることができます。その中の &self は、メソッドが呼び出された、そのインスタンス自身を指します。
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("こんにちは、{}です。{}歳です", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; alice.greet();}実行結果:
こんにちは、Aliceです。30歳ですコンストラクタのような関数
Section titled “コンストラクタのような関数”よく使われるパターンとして、impl の中にインスタンスを作ってくれる new 関数を用意する方法があります。こうすれば、呼び出す側はフィールドをひとつずつ手で書かなくて済みます。
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Bob", 25); println!("{}は{}歳です", bob.name, bob.age);}Person { name: name.to_string(), age } の中で、age だけがぽつんと書かれていることに注目してください。変数の名前がフィールドの名前とまったく同じときは、age: age と書く代わりに age だけで済ませられます。これをフィールド初期化省略記法(field init shorthand)と呼びます。
deriveで構造体を表示する
Section titled “deriveで構造体を表示する”ここまでの構造体に println!("{:?}", alice) を使おうとすると、コンパイルできません。自分で作った型をデバッグ表示する方法は、教えてあげないとRustにはわからないからです。構造体の上に #[derive(Debug)] を書けば、その機能がただで手に入ります。
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; println!("{:?}", alice);}実行結果:
Person { name: "Alice", age: 30 }#[derive(...)] は、型によくある機能を自動で作ってくれるRustのしくみです。Debug、そしてあとで出てくる Clone なども、この方法で何度も追加することになります。
1. 長方形のメソッド
Section titled “1. 長方形のメソッド”width と height(どちらも u32)のフィールドを持つ構造体 Rectangle を作ってください。メソッドを2つ持たせます。面積を返す area と、2つの辺が等しいときに true を返す is_square です。ヒント:どちらのメソッドもフィールドを読むだけなので、&self を受け取ります。
解答を見る
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("wide: 面積 {}、正方形? {}", wide.area(), wide.is_square()); println!("square: 面積 {}、正方形? {}", square.area(), square.is_square());}実行結果:
wide: 面積 24、正方形? falsesquare: 面積 25、正方形? true2. 銀行口座
Section titled “2. 銀行口座”owner(String)と、セント単位の balance(u32)を持つ構造体 Account を作ってください。残高0から始める Account::new("Ana") を書きます。それからフィールドを変更して50セントと25セントを入金し、口座をふつうの文と {:?} の2通りで表示してください。ヒント:account.balance を変更するには、変数を let mut にする必要があります。{:?} には #[derive(Debug)] が必要です。
解答を見る
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Ana"); account.balance += 50; account.balance += 25;
println!("{}の残高は{}セントです", account.owner, account.balance); println!("{:?}", account);}実行結果:
Anaの残高は75セントですAccount { owner: "Ana", balance: 75 }new が自分で balance: 0 を設定するので、呼び出す側が設定し忘れることはありません。コンストラクタ関数がこれほどよく使われる大きな理由のひとつです。
3. 人のリスト
Section titled “3. 人のリスト”3つの Person の値をベクタに入れてください。Ana(34歳)、Ben(16歳)、Chen(52歳)です。18歳以上の人の名前をすべて表示し、そのあといちばん年上の人を表示します。ヒント:for person in &people でループし、それまでで最年長の人を let mut oldest = &people[0]; で覚えておきます。
解答を見る
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Ana"), age: 34 }, Person { name: String::from("Ben"), age: 16 }, Person { name: String::from("Chen"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{}は大人です", person.name); } if person.age > oldest.age { oldest = person; } }
println!("最年長: {}({}歳)", oldest.name, oldest.age);}実行結果:
Anaは大人ですChenは大人です最年長: Chen(52歳)oldest はベクタの中を指す参照(&Person)で、コピーではありません。なので、何もクローンされません。もっと年上の人が見つかるたびに、oldest がその人を指すように付け替えているだけです。
- 構造体は、名前のついたフィールドをひとつの型にまとめます:
struct Person { name: String, age: u32 }。 - インスタンスは
Person { name: ..., age: ... }で作ります。あとでフィールドを変えたいならmutをつけます。 implブロックで振る舞いを追加します。&selfはメソッドが呼び出されたインスタンスを指します。#[derive(Debug)]をつけると、構造体を{:?}で表示できるようになります。
理解度チェック
Section titled “理解度チェック”理解度チェック
1. タプルと比べたときの、構造体のいちばんの利点は何ですか?
2. 構造体に #[derive(Debug)] をつけると、何ができるようになりますか?
3. Person { name: name.to_string(), age } の、ぽつんと書かれた age は何をしていますか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- 構造体を定義し、インスタンス化する — The Rust Programming Language 日本語版:構造体更新記法(
..other)、タプル構造体、ユニット様構造体も扱っています。 - 構造体を使ったプログラム例 — The Rust Programming Language 日本語版:長方形のプログラムを、バラバラの変数からタプル、そして構造体へと書き直していきます。構造体がなぜ役立つのかがよくわかります。
- メソッド記法 — The Rust Programming Language 日本語版:
&selfと&mut selfの違い、引数の多いメソッド、newのような関連関数について。 - 構造体 — Rust By Example 日本語版:あらゆる種類の構造体を実際に動かせる例です。最後に小さな演習もあります。
- 導出(Derive) — Rust By Example 日本語版:
Debug、Clone、PartialEqなど、#[derive(...)]で自動生成できるものがわかります。