トレイト(Trait)
共通の振る舞いを定義する「トレイト」について学びます。
トレイトとは
トレイトは型が持つべき振る舞い(メソッド)を定義します。他の言語の「インターフェース」に似ています。
#![allow(unused)]
fn main() {
trait Summary {
fn summarize(&self) -> String;
}
}
トレイトの定義と実装
トレイトを定義
#![allow(unused)]
fn main() {
trait Greet {
fn greet(&self) -> String;
}
}
型にトレイトを実装
struct Person {
name: String,
}
impl Greet for Person {
fn greet(&self) -> String {
format!("こんにちは、{}です!", self.name)
}
}
struct Robot {
id: u32,
}
impl Greet for Robot {
fn greet(&self) -> String {
format!("ピポパポ、ロボット{}号です", self.id)
}
}
fn main() {
let person = Person { name: String::from("太郎") };
let robot = Robot { id: 42 };
println!("{}", person.greet());
println!("{}", robot.greet());
}
デフォルト実装
トレイトにデフォルトの実装を提供できます。
#![allow(unused)]
fn main() {
trait Summary {
fn summarize(&self) -> String {
String::from("(詳細なし)")
}
}
struct Article {
title: String,
content: String,
}
// デフォルト実装をそのまま使う
impl Summary for Article {}
struct Tweet {
username: String,
text: String,
}
// カスタム実装で上書き
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("@{}: {}", self.username, self.text)
}
}
}
トレイト境界(引数で使う)
「このトレイトを実装した型」を引数に取れます。
#![allow(unused)]
fn main() {
// impl Trait 構文(簡潔)
fn notify(item: &impl Summary) {
println!("速報!{}", item.summarize());
}
// トレイト境界構文(より明示的)
fn notify2<T: Summary>(item: &T) {
println!("速報!{}", item.summarize());
}
}
複数のトレイト境界
#![allow(unused)]
fn main() {
fn notify(item: &(impl Summary + Display)) {
// SummaryとDisplayの両方を実装した型のみ
}
// または
fn notify<T: Summary + Display>(item: &T) {
// ...
}
}
where句(複雑な場合)
#![allow(unused)]
fn main() {
fn some_function<T, U>(t: &T, u: &U)
where
T: Summary + Clone,
U: Clone + Debug,
{
// ...
}
}
トレイトを返す
#![allow(unused)]
fn main() {
fn create_summarizable() -> impl Summary {
Tweet {
username: String::from("user"),
text: String::from("Hello!"),
}
}
}
標準ライブラリの重要なトレイト
Debug - デバッグ出力
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 1, y: 2 };
println!("{:?}", p);
}
Clone - 明示的なコピー
#[derive(Clone)]
struct Data {
value: String,
}
fn main() {
let d1 = Data { value: String::from("hello") };
let d2 = d1.clone();
}
PartialEq - 等価比較
#[derive(PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 1, y: 2 };
println!("{}", p1 == p2); // true
}
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 p = Point { x: 1, y: 2 };
println!("{}", p); // (1, 2)
}
Default - デフォルト値
#[derive(Default)]
struct Config {
debug: bool,
max_connections: u32,
}
fn main() {
let config = Config::default();
// debug: false, max_connections: 0
}
derive マクロ
よく使うトレイトはderiveで自動実装できます。
#![allow(unused)]
fn main() {
#[derive(Debug, Clone, PartialEq, Default)]
struct User {
name: String,
age: u32,
}
}
実践例: 動物の鳴き声
trait Animal {
fn name(&self) -> &str;
fn speak(&self) -> String;
fn introduce(&self) -> String {
format!("私は{}です。{}", self.name(), self.speak())
}
}
struct Dog {
name: String,
}
impl Animal for Dog {
fn name(&self) -> &str {
&self.name
}
fn speak(&self) -> String {
String::from("ワンワン!")
}
}
struct Cat {
name: String,
}
impl Animal for Cat {
fn name(&self) -> &str {
&self.name
}
fn speak(&self) -> String {
String::from("ニャー")
}
}
fn main() {
let dog = Dog { name: String::from("ポチ") };
let cat = Cat { name: String::from("タマ") };
println!("{}", dog.introduce());
println!("{}", cat.introduce());
}
まとめ
| 概念 | 説明 |
|---|---|
| トレイト | 共通の振る舞いを定義 |
impl Trait for Type | 型にトレイトを実装 |
| デフォルト実装 | トレイト内でデフォルトのメソッドを提供 |
| トレイト境界 | 「このトレイトを実装した型」という制約 |
derive | 標準トレイトの自動実装 |
確認テスト
Q1. トレイトの役割は?
正解: B) トレイトは型が持つべきメソッド(振る舞い)を定義します。複数の型が同じトレイトを実装することで、共通のインターフェースを持てます。
Q2. #[derive(Debug)]の効果は?
正解: B)
deriveマクロは指定したトレイトの実装を自動生成します。Debugを実装すると{:?}で出力できるようになります。
Q3. 以下のコードで、Silent.speak()とLoud.speak()の出力は?trait Speak { fn speak(&self) -> String { String::from("...") } } struct Silent; impl Speak for Silent {} struct Loud; impl Speak for Loud { fn speak(&self) -> String { String::from("HELLO!") } }
正解: C)
Silentはデフォルト実装を使い "..." を出力し、Loudはカスタム実装で上書きして "HELLO!" を出力します。
Q4. fn show(item: Printable)がエラーになる理由は?
正解: A) トレイトを直接型として使うことはできません。
&impl Printableや&dyn Printableのように使う必要があります。
Q5. トレイト境界T: Clone + Debugは何を意味する?
正解: D)
+は「かつ」を意味し、TはCloneとDebugの両方のトレイトを実装している型でなければなりません。
次のドキュメント: 04_generics.md