構造体(Struct)
関連するデータをまとめる「構造体」について学びます。
構造体とは
構造体は複数の関連するデータをまとめた型です。
#![allow(unused)]
fn main() {
struct User {
username: String,
email: String,
age: u32,
active: bool,
}
}
構造体の定義と使用
定義
#![allow(unused)]
fn main() {
struct Point {
x: f64,
y: f64,
}
}
インスタンスの作成
fn main() {
let p = Point {
x: 3.0,
y: 4.0,
};
println!("座標: ({}, {})", p.x, p.y);
}
フィールドへのアクセス
fn main() {
let mut user = User {
username: String::from("太郎"),
email: String::from("taro@example.com"),
age: 25,
active: true,
};
println!("名前: {}", user.username);
// mutなら変更可能
user.age = 26;
}
フィールド初期化省略記法
変数名とフィールド名が同じなら省略できます。
#![allow(unused)]
fn main() {
fn create_user(username: String, email: String) -> User {
User {
username, // username: username の省略
email, // email: email の省略
age: 0,
active: true,
}
}
}
構造体更新構文
既存のインスタンスから一部だけ変えた新しいインスタンスを作れます。
fn main() {
let user1 = User {
username: String::from("太郎"),
email: String::from("taro@example.com"),
age: 25,
active: true,
};
let user2 = User {
email: String::from("jiro@example.com"),
..user1 // 残りはuser1から
};
// 注意: user1.usernameはムーブされたので使えない
// println!("{}", user1.username); // エラー!
println!("{}", user1.age); // OK(Copyトレイト)
}
タプル構造体
フィールド名のない構造体です。
struct Color(u8, u8, u8);
struct Point3D(f64, f64, f64);
fn main() {
let black = Color(0, 0, 0);
let origin = Point3D(0.0, 0.0, 0.0);
println!("R: {}", black.0);
println!("x: {}", origin.0);
}
ユニット様構造体
フィールドを持たない構造体です。トレイト実装に使います。
struct AlwaysEqual;
fn main() {
let _subject = AlwaysEqual;
}
メソッドの定義(impl)
構造体に関連する関数をimplブロックで定義します。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// メソッド(&selfを受け取る)
fn area(&self) -> u32 {
self.width * self.height
}
// 可変メソッド(&mut selfを受け取る)
fn double(&mut self) {
self.width *= 2;
self.height *= 2;
}
// 関連関数(selfを受け取らない)
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let mut rect = Rectangle {
width: 30,
height: 50,
};
println!("面積: {}", rect.area());
rect.double();
println!("2倍後の面積: {}", rect.area());
let sq = Rectangle::square(10);
println!("正方形の面積: {}", sq.area());
}
&self、&mut self、self の違い
| 引数 | 意味 | 用途 |
|---|---|---|
&self | 不変借用 | 読み取りのみ |
&mut self | 可変借用 | 値を変更 |
self | 所有権を取得 | インスタンスを消費 |
複数のimplブロック
#![allow(unused)]
fn main() {
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
}
デバッグ出力
#[derive(Debug)]を使うと、構造体を簡単に表示できます。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("{:?}", rect); // Rectangle { width: 30, height: 50 }
println!("{:#?}", rect); // 整形表示
}
実践例: ユーザー管理
#[derive(Debug)]
struct User {
id: u32,
username: String,
email: String,
}
impl User {
fn new(id: u32, username: String, email: String) -> User {
User { id, username, email }
}
fn display(&self) {
println!("ID: {}, 名前: {}", self.id, self.username);
}
fn update_email(&mut self, new_email: String) {
self.email = new_email;
}
}
fn main() {
let mut user = User::new(1, String::from("太郎"), String::from("taro@example.com"));
user.display();
user.update_email(String::from("newtaro@example.com"));
println!("新しいメール: {}", user.email);
}
まとめ
| 概念 | 説明 |
|---|---|
| 構造体 | 関連データをまとめる |
impl | メソッドを定義 |
&self | 読み取りメソッド |
&mut self | 変更メソッド |
| 関連関数 | Self::func()で呼ぶ |
#[derive(Debug)] | デバッグ出力を有効化 |
確認テスト
Q1. メソッド定義で&selfを使う場合、何ができる?
正解: B)
&selfは不変借用なので、読み取りのみ可能です。変更するには&mut selfを使います。
Q2. Rectangle::square(10)のような呼び出しは何と呼ばれる?
正解: B)
selfを引数に取らない関数は関連関数と呼ばれ、Type::function()の形式で呼び出します。newのようなファクトリ関数によく使われます。
Q3. 以下のコードの出力は?impl Counter { fn increment(&mut self) { self.value += 1; } } let mut c = Counter { value: 0 }; c.increment(); c.increment(); println!("{}", c.value);
正解: C)
incrementメソッドを2回呼び出すので、valueは0→1→2となります。
Q4. 以下のコードがコンパイルエラーになる理由は?impl Point { fn move_by(&self, dx: f64, dy: f64) { self.x += dx; self.y += dy; } }
正解: B)
&self(不変借用)で値を変更しようとしているためエラーになります。値を変更するには&mut selfを使います。
Q5. 構造体に#[derive(Debug)]を付けると何ができる?
正解: A)
#[derive(Debug)]を使うと、println!("{:?}", value)やprintln!("{:#?}", value)で構造体の内容を表示できるようになります。
次のドキュメント: 02_enums_pattern.md