コンテンツにスキップ

Rustの構造体

構造体(struct)は、関係のある値をまとめて、名前のついたひとつの型にしたものです。タプルが名前のないちょっとした束だとすれば、構造体はすべてのフィールドに名前をつけます。そのおかげで、構造体を使うコードはぐっと読みやすくなります。

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番目が年齢、といったことを覚えておく必要はありません。

変数と同じように、構造体のインスタンスもデフォルトでは不変です。作ったあとでフィールドを変更したいなら、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歳です
▶ 実際に試してみよう

よく使われるパターンとして、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)と呼びます。

ここまでの構造体に 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 なども、この方法で何度も追加することになります。

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、正方形? false
square: 面積 25、正方形? true
▶ 実際に試してみよう

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つの 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)] をつけると、構造体を {:?} で表示できるようになります。

理解度チェック

1. タプルと比べたときの、構造体のいちばんの利点は何ですか?

2. 構造体に #[derive(Debug)] をつけると、何ができるようになりますか?

3. Person { name: name.to_string(), age } の、ぽつんと書かれた age は何をしていますか?

スコア: 0 / 3