型とトレイト -- Rustの型システムとポリモーフィズムの基盤
Rustの型システムは struct/enum による代数的データ型と trait によるアドホックポリモーフィズムを基盤とし、ジェネリクスと組み合わせてゼロコスト抽象化を実現する。
型とトレイト -- Rustの型システムとポリモーフィズムの基盤
Rustの型システムは struct/enum による代数的データ型と trait によるアドホックポリモーフィズムを基盤とし、ジェネリクスと組み合わせてゼロコスト抽象化を実現する。
この章で学ぶこと
- struct と enum -- 代数的データ型(直積型・直和型)の定義と活用を理解する
- トレイト -- インターフェースの定義、実装、デフォルトメソッドを習得する
- ジェネリクスとトレイト境界 -- 型パラメータと制約による汎用コードの書き方を学ぶ
- 動的ディスパッチと静的ディスパッチ -- トレイトオブジェクトと単態化の使い分けを理解する
- 高度なトレイトパターン -- 関連型、スーパートレイト、ブランケット実装を学ぶ
前提知識
このガイドを読む前に、以下の知識があると理解が深まります:
- 基本的なプログラミングの知識
- 関連する基礎概念の理解
- 所有権と借用 -- Rustの最も革新的なメモリ管理パラダイム の内容を理解していること
1. 基本型
1.1 プリミティブ型の一覧
| Rust プリミティブ型 | |
|---|---|
| 整数 | i8 i16 i32 i64 i128 isize |
| u8 u16 u32 u64 u128 usize | |
| 浮動小数 | f32, f64 |
| 論理 | bool |
| 文字 | char (4バイト Unicode スカラ値) |
| タプル | (T1, T2, ...) -- 異なる型の組み合わせ |
| 配列 | [T; N] -- 固定長、スタック上 |
| スライス | [T] -- 動的サイズ型(DST)、常に参照で使用 |
| 参照 | &T, &mut T |
| 文字列 | str -- 文字列スライス(DST) |
| unit | () -- 値なし(C の void に相当) |
| never | ! -- 返らない関数の戻り値型 |
1.2 数値型の詳細
fn main() {
// 整数リテラルの記法
let decimal = 98_222; // 10進数(_ で区切り可能)
let hex = 0xff; // 16進数
let octal = 0o77; // 8進数
let binary = 0b1111_0000; // 2進数
let byte = b'A'; // バイトリテラル (u8)
// 型サフィックス
let x = 42u32; // u32 型
let y = 3.14f64; // f64 型
// 型変換(as キャスト)
let a: i32 = 42;
let b: f64 = a as f64; // 拡大変換
let c: u8 = 300u16 as u8; // 縮小変換(切り捨て: 44)
// 安全な型変換
let d: u16 = 300;
let e: u8 = u8::try_from(d).unwrap_or(u8::MAX); // Result を返す
println!("decimal={}, hex={}, binary={}", decimal, hex, binary);
println!("b={}, c={}, e={}", b, c, e);
}1.3 文字列型の体系
| Rust の文字列型体系 | ||||
|---|---|---|---|---|
| 所有型 参照型(スライス) | ||||
| ┌──────────┐ ┌──────────┐ | ||||
| String | ────> | &str | Deref coercion | |
| (ヒープ) | (参照) | |||
| └──────────┘ └──────────┘ | ||||
| ┌──────────┐ ┌──────────┐ | ||||
| OsString | ────> | &OsStr | OS固有の文字列 | |
| └──────────┘ └──────────┘ | ||||
| ┌──────────┐ ┌──────────┐ | ||||
| CString | ────> | &CStr | C互換(NUL終端) | |
| └──────────┘ └──────────┘ | ||||
| ┌──────────┐ ┌──────────┐ | ||||
| PathBuf | ────> | &Path | ファイルパス | |
| └──────────┘ └──────────┘ |
fn main() {
// String (所有型) と &str (借用型)
let owned: String = String::from("hello");
let borrowed: &str = "hello"; // 文字列リテラルは &'static str
// String → &str
let slice: &str = &owned;
let slice2: &str = owned.as_str();
// &str → String
let owned2: String = borrowed.to_string();
let owned3: String = String::from(borrowed);
// 文字列の連結
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = s1 + &s2; // s1 はムーブされる、s2 は借用
// println!("{}", s1); // エラー: s1 はムーブ済み
println!("{}", s3);
// format! マクロ(どの変数もムーブしない)
let s4 = String::from("hello");
let s5 = format!("{} {}", s4, "world");
println!("{}, {}", s4, s5); // 両方有効
}2. struct(構造体)
例1: 名前付きフィールド構造体
struct User {
name: String,
email: String,
age: u32,
active: bool,
}
impl User {
// 関連関数(コンストラクタ)
fn new(name: &str, email: &str, age: u32) -> Self {
Self {
name: name.to_string(),
email: email.to_string(),
age,
active: true,
}
}
// フィールド更新構文を使ったビルダー風メソッド
fn with_active(self, active: bool) -> Self {
Self { active, ..self }
}
fn display_name(&self) -> &str {
&self.name
}
fn is_adult(&self) -> bool {
self.age >= 18
}
}
fn main() {
let user = User::new("田中", "tanaka@example.com", 30);
println!("{} ({}歳)", user.name, user.age);
// 構造体更新構文
let user2 = User {
name: String::from("鈴木"),
email: String::from("suzuki@example.com"),
..user // 残りのフィールドは user から取得(ムーブ注意)
};
println!("{} ({}歳)", user2.name, user2.age); // age=30, active=true
// メソッドチェーン
let user3 = User::new("佐藤", "sato@example.com", 15)
.with_active(false);
println!("{}は成人? {}", user3.display_name(), user3.is_adult());
}例2: タプル構造体とユニット構造体
// タプル構造体: フィールド名なし(ニュータイプパターンに有用)
struct Color(u8, u8, u8);
struct Meters(f64);
struct Celsius(f64);
struct Fahrenheit(f64);
// ニュータイプパターンで型安全性を確保
impl Celsius {
fn to_fahrenheit(&self) -> Fahrenheit {
Fahrenheit(self.0 * 9.0 / 5.0 + 32.0)
}
}
impl Fahrenheit {
fn to_celsius(&self) -> Celsius {
Celsius((self.0 - 32.0) * 5.0 / 9.0)
}
}
// ユニット構造体: フィールドなし(マーカー型に使用)
struct Marker;
struct Production;
struct Development;
fn main() {
let red = Color(255, 0, 0);
let distance = Meters(42.0);
let temp_c = Celsius(100.0);
let temp_f = temp_c.to_fahrenheit();
println!("R={}", red.0);
println!("距離={}m", distance.0);
println!("{}°C = {}°F", temp_c.0, temp_f.0);
// Meters と f64 を混同するミスを型システムが防止
// let wrong: Meters = Celsius(30.0); // コンパイルエラー!
}2.1 構造体のメモリレイアウト
| struct User のメモリレイアウト | ||
|---|---|---|
| スタック ヒープ | ||
| ┌──────────────────────┐ | ||
| name: String | ||
| ptr ─────────────────────> "田中" | ||
| len: 6 | ||
| cap: 6 | ||
| ├──────────────────────┤ | ||
| email: String | ||
| ptr ─────────────────────> "tanaka@example.com" | ||
| len: 18 | ||
| cap: 18 | ||
| ├──────────────────────┤ | ||
| age: u32 = 30 | (4バイト) | |
| ├──────────────────────┤ | ||
| active: bool = true | (1バイト + パディング) | |
| └──────────────────────┘ | ||
| コンパイラはフィールドの順序を最適化して | ||
| パディングを最小化する(repr(C) でない限り) |
3. enum(列挙型)
例3: 代数的データ型としての enum
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Triangle { base: f64, height: f64 },
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Rectangle { width, height } => width * height,
Shape::Triangle { base, height } => 0.5 * base * height,
}
}
fn perimeter(&self) -> f64 {
match self {
Shape::Circle { radius } => 2.0 * std::f64::consts::PI * radius,
Shape::Rectangle { width, height } => 2.0 * (width + height),
Shape::Triangle { base, height } => {
// 二等辺三角形と仮定
let side = ((*base / 2.0).powi(2) + height.powi(2)).sqrt();
base + 2.0 * side
}
}
}
fn describe(&self) -> String {
match self {
Shape::Circle { radius } => format!("半径{}の円", radius),
Shape::Rectangle { width, height } => format!("{}x{}の長方形", width, height),
Shape::Triangle { base, height } => format!("底辺{}, 高さ{}の三角形", base, height),
}
}
}
fn main() {
let shapes = vec![
Shape::Circle { radius: 5.0 },
Shape::Rectangle { width: 4.0, height: 6.0 },
Shape::Triangle { base: 3.0, height: 8.0 },
];
for s in &shapes {
println!("{}: 面積={:.2}, 周囲={:.2}", s.describe(), s.area(), s.perimeter());
}
}3.1 Option と Result
| Option<T> | Result<T, E> |
|---|---|
| enum Option<T> { | enum Result<T, E> { |
| Some(T), | Ok(T), |
| None, | Err(E), |
| } | } |
| 値が存在しない可能性 | 処理が失敗する可能性 |
| null の安全な代替 | 例外の安全な代替 |
3.2 高度な enum パターン
// enum にデータを持たせて状態を表現
#[derive(Debug)]
enum HttpResponse {
Ok { body: String, content_type: String },
NotFound { path: String },
Redirect { url: String, permanent: bool },
ServerError { message: String, code: u16 },
}
impl HttpResponse {
fn status_code(&self) -> u16 {
match self {
HttpResponse::Ok { .. } => 200,
HttpResponse::NotFound { .. } => 404,
HttpResponse::Redirect { permanent: true, .. } => 301,
HttpResponse::Redirect { permanent: false, .. } => 302,
HttpResponse::ServerError { code, .. } => *code,
}
}
fn is_success(&self) -> bool {
matches!(self, HttpResponse::Ok { .. })
}
}
// C互換の enum
#[repr(u8)]
enum Color {
Red = 1,
Green = 2,
Blue = 3,
}
// enum のサイズ最適化(Null Pointer Optimization)
fn size_demo() {
use std::mem::size_of;
// Option<Box<T>> は Box<T> と同じサイズ!
// None は内部的に null ポインタで表現される
assert_eq!(size_of::<Box<i32>>(), size_of::<Option<Box<i32>>>());
assert_eq!(size_of::<&i32>(), size_of::<Option<&i32>>());
println!("Box<i32>: {} bytes", size_of::<Box<i32>>());
println!("Option<Box<i32>>: {} bytes", size_of::<Option<Box<i32>>>());
}
fn main() {
let response = HttpResponse::Ok {
body: "<h1>Hello</h1>".to_string(),
content_type: "text/html".to_string(),
};
println!("ステータス: {}", response.status_code());
println!("成功?: {}", response.is_success());
size_demo();
}3.3 enum のメモリレイアウト
| enum Shape のメモリレイアウト | ||||
|---|---|---|---|---|
| 全バリアントが同じサイズのメモリを占有する | ||||
| (最大バリアントのサイズ + タグのサイズ) | ||||
| Circle: | ||||
| ┌─────────┬──────────────┬──────────────┐ | ||||
| tag = 0 | radius: f64 | (未使用) | ||
| └─────────┴──────────────┴──────────────┘ | ||||
| Rectangle: | ||||
| ┌─────────┬──────────────┬──────────────┐ | ||||
| tag = 1 | width: f64 | height: f64 | ||
| └─────────┴──────────────┴──────────────┘ | ||||
| Triangle: | ||||
| ┌─────────┬──────────────┬──────────────┐ | ||||
| tag = 2 | base: f64 | height: f64 | ||
| └─────────┴──────────────┴──────────────┘ | ||||
| サイズ = tag(1-8bytes) + max(バリアント) + padding |
4. impl ブロック
例4: メソッドと関連関数
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
// 関連関数(コンストラクタ) -- Self を引数に取らない
fn new(width: f64, height: f64) -> Self {
Self { width, height }
}
fn square(size: f64) -> Self {
Self { width: size, height: size }
}
// メソッド -- &self を引数に取る
fn area(&self) -> f64 {
self.width * self.height
}
fn perimeter(&self) -> f64 {
2.0 * (self.width + self.height)
}
fn is_square(&self) -> bool {
(self.width - self.height).abs() < f64::EPSILON
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width >= other.width && self.height >= other.height
}
// 可変メソッド
fn scale(&mut self, factor: f64) {
self.width *= factor;
self.height *= factor;
}
// self を消費するメソッド(Builder パターン等で使用)
fn into_square(self) -> Rectangle {
let side = self.width.max(self.height);
Rectangle { width: side, height: side }
}
}
// 複数の impl ブロックを持てる(トレイト実装との分離に有用)
impl std::fmt::Display for Rectangle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Rectangle({}x{})", self.width, self.height)
}
}
fn main() {
let mut rect = Rectangle::new(10.0, 5.0);
println!("{}", rect); // Rectangle(10x5)
println!("面積: {}", rect.area()); // 50.0
println!("周囲: {}", rect.perimeter()); // 30.0
println!("正方形? {}", rect.is_square()); // false
let small = Rectangle::new(3.0, 2.0);
println!("包含可能? {}", rect.can_hold(&small)); // true
rect.scale(2.0);
println!("拡大後: {}", rect); // Rectangle(20x10)
let sq = Rectangle::square(5.0);
println!("{} は正方形? {}", sq, sq.is_square()); // true
}5. トレイト
5.1 トレイト定義と実装
例5: トレイトの定義と実装
trait Summary {
// 必須メソッド
fn summarize_author(&self) -> String;
// デフォルト実装
fn summarize(&self) -> String {
format!("({}による記事をもっと読む...)", self.summarize_author())
}
// デフォルト実装が他のメソッドを呼ぶこともできる
fn preview(&self) -> String {
let summary = self.summarize();
if summary.len() > 50 {
format!("{}...", &summary[..50])
} else {
summary
}
}
}
struct Article {
title: String,
author: String,
content: String,
}
impl Summary for Article {
fn summarize_author(&self) -> String {
self.author.clone()
}
fn summarize(&self) -> String {
format!("{} -- {} (by {})", self.title, &self.content[..20], self.author)
}
}
struct Tweet {
username: String,
text: String,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
// summarize() はデフォルト実装を使用
}例6: トレイト境界付きジェネリクス
use std::fmt::{Display, Debug};
// 方法1: トレイト境界構文
fn notify<T: Summary + Display>(item: &T) {
println!("速報: {}", item.summarize());
}
// 方法2: where句 (複雑な場合に推奨)
fn complex_function<T, U>(t: &T, u: &U) -> String
where
T: Summary + Clone,
U: Display + Debug,
{
format!("{}: {:?}", t.summarize(), u)
}
// 方法3: impl Trait 構文 (引数の場合)
fn notify_simple(item: &impl Summary) {
println!("速報: {}", item.summarize());
}
// impl Trait は戻り値にも使える(ただし単一の具体型のみ)
fn create_summarizable() -> impl Summary {
Tweet {
username: String::from("rustlang"),
text: String::from("Rust is great!"),
}
}
// 複数のトレイト境界の組み合わせ
fn process_and_display<T>(item: T)
where
T: Summary + Display + Clone + Debug,
{
let cloned = item.clone();
println!("Summary: {}", item.summarize());
println!("Display: {}", item);
println!("Debug: {:?}", cloned);
}5.2 よく使う標準トレイト
| トレイト | 用途 |
|---|---|
| Display | {} フォーマット表示 |
| Debug | {:?} デバッグ表示 |
| Clone | 明示的な深いコピー (.clone()) |
| Copy | 暗黙のビットコピー |
| PartialEq/Eq | == / != 比較 |
| PartialOrd/Ord | < > <= >= 比較 / ソート |
| Hash | ハッシュ値計算(HashMapのキーに必要) |
| Default | デフォルト値生成 |
| From/Into | 型変換 |
| TryFrom/TryInto | 失敗しうる型変換 |
| Iterator | イテレータプロトコル |
| IntoIterator | for ループで使えるようにする |
| Drop | デストラクタ(スコープ終了時の処理) |
| Deref/DerefMut | 自動参照解決 / スマートポインタ |
| AsRef/AsMut | 参照としての変換 |
| Borrow | 借用としての変換(Hash/Eq の一貫性保証) |
| Send/Sync | スレッド安全性マーカー |
| Sized | コンパイル時にサイズが既知 |
| Fn/FnMut/FnOnce | クロージャ/関数呼び出し |
例7: derive マクロで自動実装
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
struct Config {
host: String,
port: u16,
debug: bool,
}
fn main() {
let config = Config {
host: "localhost".to_string(),
port: 8080,
debug: true,
};
let config2 = config.clone();
println!("{:?}", config);
println!("同じ? {}", config == config2);
let default_config = Config::default();
println!("デフォルト: {:?}", default_config);
// Config { host: "", port: 0, debug: false }
}例8: Display と From/Into の実装
use std::fmt;
#[derive(Debug)]
struct Temperature {
celsius: f64,
}
impl Temperature {
fn new(celsius: f64) -> Self {
Temperature { celsius }
}
fn fahrenheit(&self) -> f64 {
self.celsius * 9.0 / 5.0 + 32.0
}
}
// Display トレイトの手動実装
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}°C ({:.1}°F)", self.celsius, self.fahrenheit())
}
}
// From トレイトの実装(型変換)
impl From<f64> for Temperature {
fn from(celsius: f64) -> Self {
Temperature { celsius }
}
}
impl From<i32> for Temperature {
fn from(celsius: i32) -> Self {
Temperature { celsius: celsius as f64 }
}
}
// From<T> for U を実装すると、Into<U> for T が自動的に使える
fn display_temp(temp: impl Into<Temperature>) {
let t: Temperature = temp.into();
println!("{}", t);
}
fn main() {
let temp = Temperature::new(100.0);
println!("{}", temp); // Display: "100.0°C (212.0°F)"
println!("{:?}", temp); // Debug: "Temperature { celsius: 100.0 }"
// From/Into による変換
let t1: Temperature = 36.5f64.into();
let t2: Temperature = Temperature::from(100);
display_temp(25.0f64);
display_temp(0);
}例9: PartialEq と Ord の実装
#[derive(Debug, Clone)]
struct Student {
name: String,
score: u32,
}
// PartialEq: 名前が同じなら同じ学生とみなす
impl PartialEq for Student {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Student {}
// Ord: スコアでソート(降順)
impl PartialOrd for Student {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Student {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.score.cmp(&self.score) // 降順
}
}
fn main() {
let mut students = vec![
Student { name: "田中".to_string(), score: 85 },
Student { name: "鈴木".to_string(), score: 92 },
Student { name: "佐藤".to_string(), score: 78 },
];
students.sort(); // Ord に基づいてソート(スコア降順)
for s in &students {
println!("{}: {}点", s.name, s.score);
}
// 鈴木: 92点, 田中: 85点, 佐藤: 78点
}6. ジェネリクス
例10: ジェネリック構造体と関数
use std::fmt::Display;
// ジェネリック構造体
struct Pair<T> {
first: T,
second: T,
}
impl<T: PartialOrd + Display> Pair<T> {
fn new(first: T, second: T) -> Self {
Self { first, second }
}
fn larger(&self) -> &T {
if self.first >= self.second {
&self.first
} else {
&self.second
}
}
}
// 異なる型のペア
struct MixedPair<T, U> {
first: T,
second: U,
}
impl<T: Display, U: Display> MixedPair<T, U> {
fn display(&self) {
println!("({}, {})", self.first, self.second);
}
}
// 特定の型にのみ追加メソッドを提供
impl Pair<f64> {
fn average(&self) -> f64 {
(self.first + self.second) / 2.0
}
}
// ジェネリック関数
fn find_max<T: PartialOrd>(list: &[T]) -> Option<&T> {
if list.is_empty() {
return None;
}
let mut max = &list[0];
for item in &list[1..] {
if item > max {
max = item;
}
}
Some(max)
}
fn main() {
let pair = Pair::new(10, 20);
println!("大きい方: {}", pair.larger()); // 20
let float_pair = Pair::new(3.14, 2.71);
println!("平均: {}", float_pair.average()); // 2.925
let mixed = MixedPair { first: "hello", second: 42 };
mixed.display(); // (hello, 42)
let numbers = vec![34, 50, 25, 100, 65];
println!("最大値: {}", find_max(&numbers).unwrap());
}6.1 単態化(Monomorphization)
ジェネリクスのコンパイル時の処理:
ソースコード:
fn max<T: PartialOrd>(a: T, b: T) -> T { ... }
max(1i32, 2i32);
max(3.14f64, 2.71f64);
max("hello", "world");
コンパイル後(単態化):
fn max_i32(a: i32, b: i32) -> i32 { ... }
fn max_f64(a: f64, b: f64) -> f64 { ... }
fn max_str(a: &str, b: &str) -> &str { ... }
max_i32(1, 2);
max_f64(3.14, 2.71);
max_str("hello", "world");
→ 実行時のオーバーヘッドなし(ゼロコスト抽象化)
→ ただしバイナリサイズは増加する可能性あり
7. 動的ディスパッチとトレイトオブジェクト
例11: dyn Trait(動的ディスパッチ)
trait Animal {
fn name(&self) -> &str;
fn sound(&self) -> &str;
fn info(&self) -> String {
format!("{} は「{}」と鳴く", self.name(), self.sound())
}
}
struct Dog { name: String }
struct Cat { name: String }
struct Bird { name: String }
impl Animal for Dog {
fn name(&self) -> &str { &self.name }
fn sound(&self) -> &str { "ワン" }
}
impl Animal for Cat {
fn name(&self) -> &str { &self.name }
fn sound(&self) -> &str { "ニャー" }
}
impl Animal for Bird {
fn name(&self) -> &str { &self.name }
fn sound(&self) -> &str { "チュン" }
}
fn main() {
// 異なる型を同じコレクションに入れる → dyn Trait が必要
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog { name: "ポチ".to_string() }),
Box::new(Cat { name: "タマ".to_string() }),
Box::new(Bird { name: "ピー太".to_string() }),
];
for animal in &animals {
println!("{}", animal.info());
}
// 関数の引数としても使える
fn describe_animal(animal: &dyn Animal) {
println!("動物: {} - {}", animal.name(), animal.sound());
}
describe_animal(&Dog { name: "シロ".to_string() });
describe_animal(&Cat { name: "クロ".to_string() });
}7.1 vtable(仮想関数テーブル)の仕組み
トレイトオブジェクト &dyn Animal のメモリレイアウト:
ファットポインタ(2ワード) ┌──────────────┐
│ data ptr ────────────> 実際のデータ (Dog, Cat, etc.)
│ vtable ptr ──────────> vtable
└──────────────┘Dog の vtable: ┌──────────────────────┐
│ drop() │ → Dog::drop
│ size │ → sizeof(Dog)
│ align │ → alignof(Dog)
│ name() │ → Dog::name
│ sound() │ → Dog::sound
│ info() │ → Animal::info (デフォルト実装)
└──────────────────────┘Cat の vtable: ┌──────────────────────┐
│ drop() │ → Cat::drop
│ size │ → sizeof(Cat)
│ align │ → alignof(Cat)
│ name() │ → Cat::name
│ sound() │ → Cat::sound
│ info() │ → Animal::info (デフォルト実装)
└──────────────────────┘
7.2 オブジェクト安全性
// オブジェクト安全なトレイト(dyn Trait として使用可能)
trait Drawable {
fn draw(&self);
fn bounding_box(&self) -> (f64, f64, f64, f64);
}
// オブジェクト安全でないトレイト(dyn Trait として使用不可)
trait NotObjectSafe {
fn create() -> Self; // Self を返す関連関数
fn compare(&self, other: &Self); // Self を引数に取る
fn generic_method<T>(&self, t: T); // ジェネリックメソッド
}
// オブジェクト安全の条件:
// 1. Self: Sized を要求しない
// 2. メソッドの戻り値に Self を使わない(where Self: Sized ガード付きは除く)
// 3. ジェネリックな型パラメータを持たない
// 4. 関連定数を持たない
// 部分的にオブジェクト安全にする技法
trait Clonable: Clone {
fn clone_box(&self) -> Box<dyn Clonable>;
}
impl<T: Clone + Clonable + 'static> Clonable for T {
fn clone_box(&self) -> Box<dyn Clonable> {
Box::new(self.clone())
}
}8. 高度なトレイトパターン
8.1 関連型 (Associated Types)
// 関連型を使ったイテレータの定義
trait MyIterator {
type Item; // 関連型
fn next(&mut self) -> Option<Self::Item>;
}
struct Counter {
count: u32,
max: u32,
}
impl MyIterator for Counter {
type Item = u32; // 関連型を具体化
fn next(&mut self) -> Option<Self::Item> {
if self.count < self.max {
self.count += 1;
Some(self.count)
} else {
None
}
}
}
// 関連型 vs ジェネリクスの比較
// 関連型: 1つの型に対して1つの実装のみ
// ジェネリクス: 1つの型に対して複数の実装が可能
// ジェネリクス版(複数の変換先を定義可能)
trait ConvertTo<T> {
fn convert(&self) -> T;
}
struct Celsius(f64);
impl ConvertTo<f64> for Celsius {
fn convert(&self) -> f64 { self.0 }
}
impl ConvertTo<String> for Celsius {
fn convert(&self) -> String { format!("{}°C", self.0) }
}8.2 スーパートレイト
use std::fmt;
// Display を要求するトレイト(スーパートレイト)
trait Printable: fmt::Display + fmt::Debug {
fn print(&self) {
println!("Display: {}", self);
}
fn debug_print(&self) {
println!("Debug: {:?}", self);
}
fn pretty_print(&self) {
println!("===== {} =====", self);
}
}
#[derive(Debug)]
struct Report {
title: String,
content: String,
}
impl fmt::Display for Report {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}] {}", self.title, self.content)
}
}
// Display + Debug を実装しているので Printable を実装可能
impl Printable for Report {}
fn main() {
let report = Report {
title: "月次報告".to_string(),
content: "売上は前月比10%増".to_string(),
};
report.print();
report.debug_print();
report.pretty_print();
}8.3 ブランケット実装
// ブランケット実装: 条件を満たす全ての型に対して一括実装
trait Greet {
fn greet(&self) -> String;
}
// Display を実装している全ての型に Greet を実装
impl<T: std::fmt::Display> Greet for T {
fn greet(&self) -> String {
format!("こんにちは、{}さん!", self)
}
}
fn main() {
println!("{}", "太郎".greet()); // こんにちは、太郎さん!
println!("{}", 42.greet()); // こんにちは、42さん!
println!("{}", 3.14f64.greet()); // こんにちは、3.14さん!
}8.4 演算子オーバーロード
use std::ops::{Add, Mul, Neg};
#[derive(Debug, Clone, Copy, PartialEq)]
struct Vector2D {
x: f64,
y: f64,
}
impl Vector2D {
fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
fn magnitude(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
fn dot(&self, other: &Self) -> f64 {
self.x * other.x + self.y * other.y
}
}
// + 演算子
impl Add for Vector2D {
type Output = Self;
fn add(self, other: Self) -> Self::Output {
Self {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
// * 演算子(スカラー倍)
impl Mul<f64> for Vector2D {
type Output = Self;
fn mul(self, scalar: f64) -> Self::Output {
Self {
x: self.x * scalar,
y: self.y * scalar,
}
}
}
// - 演算子(符号反転)
impl Neg for Vector2D {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
x: -self.x,
y: -self.y,
}
}
}
// Display
impl std::fmt::Display for Vector2D {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
fn main() {
let v1 = Vector2D::new(3.0, 4.0);
let v2 = Vector2D::new(1.0, 2.0);
println!("v1 + v2 = {}", v1 + v2); // (4, 6)
println!("v1 * 2 = {}", v1 * 2.0); // (6, 8)
println!("-v1 = {}", -v1); // (-3, -4)
println!("|v1| = {}", v1.magnitude()); // 5
println!("v1 . v2 = {}", v1.dot(&v2)); // 11
}9. 比較表
9.1 struct vs enum
| 特性 | struct | enum |
|---|---|---|
| 代数型 | 直積型 (product type) | 直和型 (sum type) |
| フィールド | 全フィールド同時に持つ | バリアントの1つだけ |
| パターンマッチ | 分解代入 | match で網羅的に分岐 |
| メモリ | 全フィールドの合計 | 最大バリアント + タグ |
| 用途 | データのまとまり | 状態・選択肢の表現 |
| 型安全性 | フィールドの型で保証 | バリアントの網羅性で保証 |
9.2 静的ディスパッチ vs 動的ディスパッチ
| 特性 | 静的 (impl Trait / ジェネリクス) | 動的 (dyn Trait) |
|---|---|---|
| 仕組み | 単態化 (monomorphization) | vtable (仮想関数テーブル) |
| 実行速度 | 高速(インライン化可能) | オーバーヘッドあり |
| バイナリサイズ | 大きくなりやすい | 小さい |
| 型消去 | なし(コンパイル時に具体型決定) | あり |
| 使い方 | fn f(x: impl Trait) |
fn f(x: &dyn Trait) |
| オブジェクト安全 | 不要 | 必要 |
| コレクション | 同一型のみ | 異なる型を混在可能 |
| コンパイル時間 | 型ごとにコード生成(遅くなりうる) | コード共有(速い) |
9.3 トレイト関連の比較
| パターン | 記法 | 用途 |
|---|---|---|
| トレイト境界 | T: Clone + Debug |
ジェネリック関数の制約 |
| where句 | where T: Clone |
複雑な境界を読みやすく |
| impl Trait (引数) | item: &impl Summary |
簡潔なトレイト境界 |
| impl Trait (戻り値) | -> impl Summary |
具体型を隠す |
| dyn Trait | &dyn Summary |
動的ディスパッチ |
| Box |
Box<dyn Summary> |
ヒープ上のトレイトオブジェクト |
| 関連型 | type Item = u32; |
型ごとに1つの関連型 |
| derive | #[derive(Debug)] |
標準トレイトの自動実装 |
10. アンチパターン
アンチパターン1: String フィールドに &str を入れようとする
// BAD: ライフタイムが必要で複雑になる
// struct User<'a> {
// name: &'a str, // ライフタイム注釈が伝播して大変
// }
// GOOD: 所有型を使う (ほとんどのケースで推奨)
struct User {
name: String, // 構造体は自身のデータを所有する
}アンチパターン2: トレイトオブジェクトの不必要な使用
// BAD: ジェネリクスで十分なのに動的ディスパッチ
fn process(items: &[Box<dyn Summary>]) {
for item in items {
println!("{}", item.summarize());
}
}
// GOOD: 同一型なら静的ディスパッチ
fn process_good<T: Summary>(items: &[T]) {
for item in items {
println!("{}", item.summarize());
}
}
// 注: 異なる型を混在させるならdyn Traitが正解アンチパターン3: 不必要に複雑なトレイト境界
// BAD: 使わないトレイト境界を付ける
fn print_item<T: Display + Debug + Clone + Send + Sync>(item: &T) {
println!("{}", item); // Display しか使っていない
}
// GOOD: 必要最小限のトレイト境界
fn print_item_good<T: Display>(item: &T) {
println!("{}", item);
}アンチパターン4: enum の過度な使用
// BAD: 状態が追加されるたびに全てのmatch式を修正する必要がある
enum Shape {
Circle(f64),
Rectangle(f64, f64),
Triangle(f64, f64),
// Pentagon, Hexagon, ... と増えていく
}
fn area(shape: &Shape) -> f64 {
match shape {
Shape::Circle(r) => std::f64::consts::PI * r * r,
Shape::Rectangle(w, h) => w * h,
Shape::Triangle(b, h) => 0.5 * b * h,
// 新しいバリアントを追加するたびにここも修正
}
}
// GOOD: トレイトを使えば拡張に開かれた設計になる
trait ShapeTrait {
fn area(&self) -> f64;
}
// 新しい形状は新しい struct + impl で追加するだけ
struct Pentagon { side: f64 }
impl ShapeTrait for Pentagon {
fn area(&self) -> f64 {
// 正五角形の面積
0.25 * (5.0f64).sqrt() * (5.0 + 2.0 * (5.0f64).sqrt()) * self.side.powi(2)
}
}実践演習
演習1: 基本的な実装
以下の要件を満たすコードを実装してください。
要件:
- 入力データの検証を行うこと
- エラーハンドリングを適切に実装すること
- テストコードも作成すること
# 演習1: 基本実装のテンプレート
class Exercise1:
"""基本的な実装パターンの演習"""
def __init__(self):
self.data = []
def validate_input(self, value):
"""入力値の検証"""
if value is None:
raise ValueError("入力値がNoneです")
return True
def process(self, value):
"""データ処理のメインロジック"""
self.validate_input(value)
self.data.append(value)
return self.data
def get_results(self):
"""処理結果の取得"""
return {
'count': len(self.data),
'data': self.data
}
# テスト
def test_exercise1():
ex = Exercise1()
assert ex.process(1) == [1]
assert ex.process(2) == [1, 2]
assert ex.get_results()['count'] == 2
try:
ex.process(None)
assert False, "例外が発生するべき"
except ValueError:
pass
print("全テスト合格!")
test_exercise1()演習2: 応用パターン
基本実装を拡張して、以下の機能を追加してください。
# 演習2: 応用パターン
from typing import List, Dict, Optional
from datetime import datetime
class AdvancedExercise:
"""応用パターンの演習"""
def __init__(self, max_size: int = 100):
self._items: List[Dict] = []
self._max_size = max_size
self._created_at = datetime.now()
def add(self, key: str, value: any) -> bool:
"""アイテムの追加(サイズ制限付き)"""
if len(self._items) >= self._max_size:
return False
self._items.append({
'key': key,
'value': value,
'timestamp': datetime.now().isoformat()
})
return True
def find(self, key: str) -> Optional[Dict]:
"""キーによる検索"""
for item in reversed(self._items):
if item['key'] == key:
return item
return None
def remove(self, key: str) -> bool:
"""キーによる削除"""
for i, item in enumerate(self._items):
if item['key'] == key:
self._items.pop(i)
return True
return False
def stats(self) -> Dict:
"""統計情報"""
return {
'total_items': len(self._items),
'max_size': self._max_size,
'usage_percent': len(self._items) / self._max_size * 100,
'uptime': str(datetime.now() - self._created_at)
}
# テスト
def test_advanced():
ex = AdvancedExercise(max_size=3)
assert ex.add("a", 1) == True
assert ex.add("b", 2) == True
assert ex.add("c", 3) == True
assert ex.add("d", 4) == False # サイズ制限
assert ex.find("b")['value'] == 2
assert ex.remove("b") == True
assert ex.find("b") is None
stats = ex.stats()
assert stats['total_items'] == 2
print("応用テスト全合格!")
test_advanced()演習3: パフォーマンス最適化
以下のコードのパフォーマンスを改善してください。
# 演習3: パフォーマンス最適化
import time
from functools import lru_cache
# 最適化前(O(n^2))
def slow_search(data: list, target: int) -> int:
"""非効率な検索"""
for i in range(len(data)):
for j in range(i + 1, len(data)):
if data[i] + data[j] == target:
return (i, j)
return (-1, -1)
# 最適化後(O(n))
def fast_search(data: list, target: int) -> tuple:
"""ハッシュマップを使った効率的な検索"""
seen = {}
for i, num in enumerate(data):
complement = target - num
if complement in seen:
return (seen[complement], i)
seen[num] = i
return (-1, -1)
# ベンチマーク
def benchmark():
import random
data = list(range(5000))
random.shuffle(data)
target = data[100] + data[4000]
start = time.time()
result1 = slow_search(data, target)
slow_time = time.time() - start
start = time.time()
result2 = fast_search(data, target)
fast_time = time.time() - start
print(f"非効率版: {slow_time:.4f}秒")
print(f"効率版: {fast_time:.6f}秒")
print(f"高速化率: {slow_time/fast_time:.0f}倍")
benchmark()ポイント:
- アルゴリズムの計算量を意識する
- 適切なデータ構造を選択する
- ベンチマークで効果を測定する
トラブルシューティング
よくあるエラーと解決策
| エラー | 原因 | 解決策 |
|---|---|---|
| 初期化エラー | 設定ファイルの不備 | 設定ファイルのパスと形式を確認 |
| タイムアウト | ネットワーク遅延/リソース不足 | タイムアウト値の調整、リトライ処理の追加 |
| メモリ不足 | データ量の増大 | バッチ処理の導入、ページネーションの実装 |
| 権限エラー | アクセス権限の不足 | 実行ユーザーの権限確認、設定の見直し |
| データ不整合 | 並行処理の競合 | ロック機構の導入、トランザクション管理 |
デバッグの手順
- エラーメッセージの確認: スタックトレースを読み、発生箇所を特定する
- 再現手順の確立: 最小限のコードでエラーを再現する
- 仮説の立案: 考えられる原因をリストアップする
- 段階的な検証: ログ出力やデバッガを使って仮説を検証する
- 修正と回帰テスト: 修正後、関連する箇所のテストも実行する
# デバッグ用ユーティリティ
import logging
import traceback
from functools import wraps
# ロガーの設定
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s'
)
logger = logging.getLogger(__name__)
def debug_decorator(func):
"""関数の入出力をログ出力するデコレータ"""
@wraps(func)
def wrapper(*args, **kwargs):
logger.debug(f"呼び出し: {func.__name__}(args={args}, kwargs={kwargs})")
try:
result = func(*args, **kwargs)
logger.debug(f"戻り値: {func.__name__} -> {result}")
return result
except Exception as e:
logger.error(f"例外発生: {func.__name__}: {e}")
logger.error(traceback.format_exc())
raise
return wrapper
@debug_decorator
def process_data(items):
"""データ処理(デバッグ対象)"""
if not items:
raise ValueError("空のデータ")
return [item * 2 for item in items]パフォーマンス問題の診断
パフォーマンス問題が発生した場合の診断手順:
- ボトルネックの特定: プロファイリングツールで計測
- メモリ使用量の確認: メモリリークの有無をチェック
- I/O待ちの確認: ディスクやネットワークI/Oの状況を確認
- 同時接続数の確認: コネクションプールの状態を確認
| 問題の種類 | 診断ツール | 対策 |
|---|---|---|
| CPU負荷 | cProfile, py-spy | アルゴリズム改善、並列化 |
| メモリリーク | tracemalloc, objgraph | 参照の適切な解放 |
| I/Oボトルネック | strace, iostat | 非同期I/O、キャッシュ |
| DB遅延 | EXPLAIN, slow query log | インデックス、クエリ最適化 |
11. FAQ
Q1: struct のフィールドを String にするか &str にするかの基準は?
A: 原則として構造体には所有型(String)を使います。構造体が独立してデータを管理でき、ライフタイムの伝播を避けられます。パフォーマンスが重要で短命な構造体(パーサーの中間結果など)の場合のみ &str + ライフタイムを検討してください。
Q2: impl Trait と dyn Trait はどう使い分けますか?
A:
impl Trait: コンパイル時に具体型が決まる場合。高速で型安全。dyn Trait: 実行時に異なる型を扱う場合。Vec<Box<dyn Trait>>のようにコレクションに異なる型を入れたいとき。
Q3: trait を実装するとき、孤児ルールとは何ですか?
A: 他のクレートが定義したトレイトを、他のクレートが定義した型に実装することはできません。少なくとも型またはトレイトのどちらかが自分のクレートで定義されている必要があります。これにより実装の衝突を防ぎます。
// OK: 自分のトレイトを外部の型に実装
impl MyTrait for Vec<i32> { ... }
// OK: 外部のトレイトを自分の型に実装
impl Display for MyStruct { ... }
// NG: 外部のトレイトを外部の型に実装
// impl Display for Vec<i32> { ... } // コンパイルエラーQ4: 関連型とジェネリクスはどう違いますか?
A:
- 関連型: 1つの型に対して1つの実装のみ可能。Iterator の Item が代表例
- ジェネリクス: 1つの型に対して複数の実装が可能。From
が代表例
// 関連型: Vec<i32> の Iterator は Item=&i32 のみ
impl Iterator for MyIter {
type Item = u32; // 固定
fn next(&mut self) -> Option<u32> { ... }
}
// ジェネリクス: 同じ型に複数の From を実装可能
impl From<String> for MyType { ... }
impl From<i32> for MyType { ... }Q5: derive できるトレイトの一覧は?
A: 標準ライブラリでは以下のトレイトが derive 可能です:
Debug,Clone,CopyPartialEq,EqPartialOrd,OrdHash,Default
外部クレートでは serde::Serialize, serde::Deserialize, thiserror::Error なども derive 可能です。
FAQ
Q1: このトピックを学ぶ上で最も重要なポイントは何ですか?
実践的な経験を積むことが最も重要です。理論だけでなく、実際にコードを書いて動作を確認することで理解が深まります。
Q2: 初心者がよく陥る間違いは何ですか?
基礎を飛ばして応用に進むことです。このガイドで説明している基本概念をしっかり理解してから、次のステップに進むことをお勧めします。
Q3: 実務ではどのように活用されていますか?
このトピックの知識は、日常的な開発業務で頻繁に活用されます。特にコードレビューやアーキテクチャ設計の際に重要になります。
12. まとめ
| 概念 | 要点 |
|---|---|
| struct | 名前付き/タプル/ユニットの3種類。直積型 |
| enum | バリアントを持つ直和型。パターンマッチで分岐 |
| impl | メソッドと関連関数を定義するブロック |
| trait | インターフェース定義。デフォルト実装も可能 |
| ジェネリクス | 型パラメータで汎用コードを記述 |
| トレイト境界 | ジェネリクスに制約を付ける (T: Clone + Debug) |
| derive | 標準トレイトの自動実装 |
| 静的/動的ディスパッチ | 単態化 vs vtable。用途に応じて選択 |
| 関連型 | トレイト内で定義する型パラメータ |
| 演算子オーバーロード | std::ops のトレイトを実装 |
| ブランケット実装 | 条件を満たす全型への一括実装 |
次に読むべきガイド
- 03-error-handling.md -- Result/Option を活用したエラー処理
- 04-collections-iterators.md -- コレクションとイテレータ
- ../01-advanced/00-lifetimes.md -- ライフタイム詳解
参考文献
- The Rust Programming Language - Ch.5 Structs, Ch.6 Enums, Ch.10 Generics/Traits -- https://doc.rust-lang.org/book/
- Rust by Example - Custom Types -- https://doc.rust-lang.org/rust-by-example/custom_types.html
- Rust API Guidelines - Type Safety -- https://rust-lang.github.io/api-guidelines/type-safety.html
- The Rustonomicon - Trait Objects -- https://doc.rust-lang.org/nomicon/exotic-sizes.html
- Rust Design Patterns -- https://rust-unofficial.github.io/patterns/