第10章: 構造体
学習目標
- 構造体の定義と使い方を理解する
- メソッドと関連関数を実装する
- implブロックの役割を把握する
- タプル構造体とユニット構造体を学ぶ
---
10.1 構造体の基本
10.1.1 構造体の定義
複数の関連する値をまとめたデータ型:
struct User {
username: String,
email: String,
age: u32,
active: bool,
}
fn main() {
let user = User {
username: String::from("alice"),
email: String::from("alice@example.com"),
age: 30,
active: true,
};
println!("ユーザー名: {}", user.username);
}
構文:
struct 構造体名 {
フィールド名: 型,
...
}
10.1.2 フィールドへのアクセス
ドット記法でアクセス:
fn main() {
let user = User {
username: String::from("bob"),
email: String::from("bob@example.com"),
age: 25,
active: true,
};
println!("名前: {}", user.username);
println!("年齢: {}", user.age);
}
10.1.3 可変な構造体
fn main() {
let mut user = User {
username: String::from("alice"),
email: String::from("alice@example.com"),
age: 30,
active: true,
};
user.email = String::from("newemail@example.com");
user.age += 1;
println!("新しいメール: {}", user.email);
}
注意:構造体全体がmutかimmutか(個別フィールドごとには指定できない)
---
10.2 構造体の生成
10.2.1 フィールド初期化省略記法
変数名とフィールド名が同じ場合:
fn build_user(username: String, email: String) -> User {
User {
username, // username: username の省略形
email, // email: email の省略形
age: 0,
active: true,
}
}
10.2.2 構造体更新記法
既存の構造体から一部を変更:
fn main() {
let user1 = User {
username: String::from("alice"),
email: String::from("alice@example.com"),
age: 30,
active: true,
};
let user2 = User {
email: String::from("different@example.com"),
..user1 // 残りのフィールドはuser1と同じ
};
// println!("{}", user1.username); // ❌ エラー!usernameはムーブ済み
println!("{}", user1.age); // ✅ OK(Copyトレイト)
}
注意点:
..は最後に記述- Stringなどの非Copyフィールドは所有権が移動
---
10.3 タプル構造体
10.3.1 基本的な定義
名前付きフィールドがない構造体:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("R: {}, G: {}, B: {}", black.0, black.1, black.2);
}
用途:
- 型に意味を持たせたい
- フィールド名が不要
10.3.2 newtype パターン
型安全性のために1つのフィールドを持つ構造体:
struct Meters(f64);
struct Kilometers(f64);
fn distance_in_meters(d: Meters) -> f64 {
d.0
}
fn main() {
let m = Meters(100.0);
let km = Kilometers(1.0);
println!("{}", distance_in_meters(m));
// println!("{}", distance_in_meters(km)); // ❌ 型が違う
}
---
10.4 ユニット構造体
10.4.1 フィールドがない構造体
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
用途:
- トレイトの実装が必要だがデータは不要な場合
- マーカー型として使用
trait Greet {
fn greet(&self);
}
struct English;
struct Japanese;
impl Greet for English {
fn greet(&self) {
println!("Hello!");
}
}
impl Greet for Japanese {
fn greet(&self) {
println!("こんにちは!");
}
}
fn main() {
let en = English;
let ja = Japanese;
en.greet();
ja.greet();
}
---
10.5 メソッド
10.5.1 implブロック
構造体にメソッドを実装:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// メソッド(&selfを取る)
fn area(&self) -> u32 {
self.width * self.height
}
fn perimeter(&self) -> u32 {
2 * (self.width + self.height)
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect = Rectangle {
width: 30,
height: 50,
};
println!("面積: {}", rect.area());
println!("周囲: {}", rect.perimeter());
}
10.5.2 selfの3つの形
┌─────────────────────────────────────────────────────────┐
│ selfの種類 │
├─────────────────────────────────────────────────────────┤
│ │
│ &self │
│ - 不変借用 │
│ - 値を読むだけ │
│ - 最も一般的 │
│ │
│ &mut self │
│ - 可変借用 │
│ - 値を変更する │
│ │
│ self │
│ - 所有権を取る │
│ - 値を消費する │
│ - 変換メソッドなど │
│ │
└─────────────────────────────────────────────────────────┘
例:
impl Rectangle {
// 不変借用
fn width(&self) -> u32 {
self.width
}
// 可変借用
fn set_width(&mut self, width: u32) {
self.width = width;
}
// 所有権を取る
fn into_square(self) -> Rectangle {
let size = std::cmp::min(self.width, self.height);
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect = Rectangle { width: 30, height: 50 };
println!("幅: {}", rect.width());
let mut rect2 = Rectangle { width: 30, height: 50 };
rect2.set_width(40);
let square = rect.into_square();
// println!("{}", rect.width); // ❌ rectは消費された
}
---
10.6 関連関数
10.6.1 コンストラクタパターン
new関数を定義するのが慣習:
impl Rectangle {
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect = Rectangle::new(30, 50);
let square = Rectangle::square(20);
}
特徴:
selfを取らない構造体名::関数名()で呼び出す- コンストラクタとして使用
10.6.2 ファクトリーメソッド
impl Rectangle {
fn from_dimensions(width: u32, height: u32) -> Self {
Self { width, height }
}
fn default() -> Self {
Self {
width: 1,
height: 1,
}
}
}
fn main() {
let rect1 = Rectangle::from_dimensions(10, 20);
let rect2 = Rectangle::default();
}
Selfキーワード:
- 現在の型を表す
- 型名の繰り返しを避ける
---
10.7 複数のimplブロック
同じ構造体に複数のimplブロックを持てます:
impl Rectangle {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn is_square(&self) -> bool {
self.width == self.height
}
}
用途:
- ジェネリクスのトレイト境界ごとに分ける
- 関連する機能をグループ化
---
10.8 デバッグ出力
10.8.1 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);
// Rectangle {
// width: 30,
// height: 50,
// }
}
10.8.2 カスタムDisplay
use std::fmt;
struct Point {
x: i32,
y: i32,
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let point = Point { x: 10, y: 20 };
println!("{}", point); // (10, 20)
}
---
10.9 実践例
例1:ユーザー管理システム
#[derive(Debug)]
struct User {
id: u64,
username: String,
email: String,
active: bool,
}
impl User {
fn new(id: u64, username: String, email: String) -> Self {
Self {
id,
username,
email,
active: true,
}
}
fn deactivate(&mut self) {
self.active = false;
}
fn update_email(&mut self, new_email: String) {
self.email = new_email;
}
fn is_active(&self) -> bool {
self.active
}
}
fn main() {
let mut user = User::new(
1,
String::from("alice"),
String::from("alice@example.com"),
);
println!("アクティブ: {}", user.is_active());
user.deactivate();
println!("アクティブ: {}", user.is_active());
}
例2:図形の計算
#[derive(Debug)]
struct Circle {
radius: f64,
}
impl Circle {
fn new(radius: f64) -> Self {
Self { radius }
}
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
fn circumference(&self) -> f64 {
2.0 * std::f64::consts::PI * self.radius
}
fn scale(&mut self, factor: f64) {
self.radius *= factor;
}
}
fn main() {
let mut circle = Circle::new(5.0);
println!("半径: {}", circle.radius);
println!("面積: {:.2}", circle.area());
println!("円周: {:.2}", circle.circumference());
circle.scale(2.0);
println!("スケール後の半径: {}", circle.radius);
}
例3:銀行口座
#[derive(Debug)]
struct BankAccount {
account_number: String,
balance: f64,
owner: String,
}
impl BankAccount {
fn new(account_number: String, owner: String) -> Self {
Self {
account_number,
balance: 0.0,
owner,
}
}
fn deposit(&mut self, amount: f64) -> Result<(), String> {
if amount <= 0.0 {
return Err(String::from("金額は正の数である必要があります"));
}
self.balance += amount;
Ok(())
}
fn withdraw(&mut self, amount: f64) -> Result<(), String> {
if amount <= 0.0 {
return Err(String::from("金額は正の数である必要があります"));
}
if amount > self.balance {
return Err(String::from("残高不足"));
}
self.balance -= amount;
Ok(())
}
fn balance(&self) -> f64 {
self.balance
}
}
fn main() {
let mut account = BankAccount::new(
String::from("123-456"),
String::from("Alice"),
);
account.deposit(1000.0).unwrap();
println!("残高: {}", account.balance());
match account.withdraw(500.0) {
Ok(_) => println!("引き出し成功。残高: {}", account.balance()),
Err(e) => println!("エラー: {}", e),
}
}
例4:タスク管理
#[derive(Debug, PartialEq)]
enum TaskStatus {
Todo,
InProgress,
Done,
}
#[derive(Debug)]
struct Task {
id: u32,
title: String,
description: String,
status: TaskStatus,
}
impl Task {
fn new(id: u32, title: String, description: String) -> Self {
Self {
id,
title,
description,
status: TaskStatus::Todo,
}
}
fn start(&mut self) {
if self.status == TaskStatus::Todo {
self.status = TaskStatus::InProgress;
}
}
fn complete(&mut self) {
self.status = TaskStatus::Done;
}
fn is_done(&self) -> bool {
self.status == TaskStatus::Done
}
}
fn main() {
let mut task = Task::new(
1,
String::from("Rustを学ぶ"),
String::from("構造体の章を完了する"),
);
println!("{:?}", task.status);
task.start();
println!("{:?}", task.status);
task.complete();
println!("完了: {}", task.is_done());
}
---
10.10 構造体のパターン
10.10.1 ビルダーパターン
struct User {
username: String,
email: String,
age: Option<u32>,
}
struct UserBuilder {
username: String,
email: String,
age: Option<u32>,
}
impl UserBuilder {
fn new(username: String, email: String) -> Self {
Self {
username,
email,
age: None,
}
}
fn age(mut self, age: u32) -> Self {
self.age = Some(age);
self
}
fn build(self) -> User {
User {
username: self.username,
email: self.email,
age: self.age,
}
}
}
fn main() {
let user = UserBuilder::new(
String::from("alice"),
String::from("alice@example.com"),
)
.age(30)
.build();
}
10.10.2 状態パターン
struct Draft {
content: String,
}
struct PendingReview {
content: String,
}
struct Published {
content: String,
}
impl Draft {
fn new(content: String) -> Self {
Self { content }
}
fn request_review(self) -> PendingReview {
PendingReview {
content: self.content,
}
}
}
impl PendingReview {
fn approve(self) -> Published {
Published {
content: self.content,
}
}
fn reject(self) -> Draft {
Draft {
content: self.content,
}
}
}
fn main() {
let draft = Draft::new(String::from("記事の内容"));
let review = draft.request_review();
let published = review.approve();
}
---
10.11 まとめ
学んだこと
- メソッド
- パターン
- 実践
次のステップ
Rust Foundations コースの基礎編は完了です。次は:
- 列挙型とパターンマッチング(詳細)
- エラーハンドリング
- ジェネリクスとトレイト
- コレクション型
- モジュールとクレート
- The Rust Programming Language - Chapter 5
- Rust By Example - Structures
- API Guidelines - Naming
---