トレイトとは何か?
Rustの**トレイト(trait)**は、型が「どんな振る舞いを持つか」を定義する仕組みです。他の言語のインターフェースに近い概念ですが、Rustならではの強力な機能を持っています。
たとえば「面積を計算できる」という振る舞いをトレイトとして定義しておけば、三角形でも円でも、同じメソッド名で面積を求めることができます。コードの再利用性と型安全性を両立できるのがトレイトの魅力です。
トレイトの基本的な定義と実装
まずはシンプルな例からはじめましょう。Areaというトレイトを定義し、RectangleとCircleに実装してみます。
// トレイトの定義
trait Area {
fn area(&self) -> f64;
}
// 長方形の構造体
struct Rectangle {
width: f64,
height: f64,
}
// 円の構造体
struct Circle {
radius: f64,
}
// Rectangle に Area トレイトを実装
impl Area for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// Circle に Area トレイトを実装
impl Area for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
fn main() {
let rect = Rectangle { width: 4.0, height: 3.0 };
let circle = Circle { radius: 2.0 };
println!("長方形の面積: {:.2}", rect.area()); // 12.00
println!("円の面積: {:.2}", circle.area()); // 12.57
}
trait トレイト名 { ... } でトレイトを定義し、impl トレイト名 for 型名 { ... } で各型に実装します。これだけで、異なる型に共通のインターフェースを持たせることができます。
デフォルトメソッド
トレイトのメソッドにはデフォルト実装を持たせることができます。個別の型でオーバーライドしなければデフォルトの動作が使われるので、実装の手間を省けます。
trait Greet {
fn name(&self) -> &str;
// デフォルト実装
fn greet(&self) {
println!("こんにちは、{}さん!", self.name());
}
}
struct User {
name: String,
}
impl Greet for User {
fn name(&self) -> &str {
&self.name
}
// greet() はデフォルト実装をそのまま使う
}
struct Bot {
name: String,
}
impl Greet for Bot {
fn name(&self) -> &str {
&self.name
}
// Bot は greet() をオーバーライド
fn greet(&self) {
println!("私はボット「{}」です。ご用件をどうぞ。", self.name());
}
}
fn main() {
let user = User { name: String::from("Alice") };
let bot = Bot { name: String::from("R2D2") };
user.greet(); // こんにちは、Aliceさん!
bot.greet(); // 私はボット「R2D2」です。ご用件をどうぞ。
}
name() は必ず実装しなければなりませんが、greet() はデフォルト実装があるためオーバーライドは任意です。
トレイト境界:ジェネリクスと組み合わせる
トレイトが本領を発揮するのがトレイト境界です。ジェネリクスと組み合わせることで、「特定のトレイトを実装した型のみ受け付ける」関数を作れます。
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
content: String,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("「{}」: {}...", self.title, &self.content[..20])
}
}
// トレイト境界を使った関数(impl Trait 構文)
fn print_summary(item: &impl Summary) {
println!("{}", item.summarize());
}
// where 句を使った書き方(複雑な場合に読みやすい)
fn print_summary_where<T>(item: &T)
where
T: Summary,
{
println!("{}", item.summarize());
}
fn main() {
let article = Article {
title: String::from("Rustトレイト入門"),
content: String::from("トレイトはRustの中心的な概念です。"),
};
print_summary(&article);
print_summary_where(&article);
}
impl Trait 構文と where 句はどちらも同じ意味ですが、トレイト境界が増えるときは where 句を使うと読みやすくなります。
標準ライブラリのよく使うトレイト
Rustの標準ライブラリには便利なトレイトが多数用意されています。
| トレイト | 役割 |
|---|---|
Display | println!("{}", ...) でフォーマット |
Debug | println!("{:?}", ...) でデバッグ出力 |
Clone | 値の複製(.clone()) |
PartialEq | == による比較 |
Iterator | イテレータの振る舞いを定義 |
#[derive(...)] 属性を使うと、これらのトレイトを自動実装できます。
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone();
println!("{:?}", p1); // Point { x: 1.0, y: 2.0 }
println!("等しい: {}", p1 == p2); // 等しい: true
}
まとめ
- トレイトは型の振る舞いを定義する仕組みで、
traitキーワードで宣言する impl トレイト for 型で各型に実装する- デフォルトメソッドを使うと共通の処理をトレイト側にまとめられる
- トレイト境界とジェネリクスを組み合わせると、柔軟で再利用しやすいコードが書ける
- 標準ライブラリのトレイトは
#[derive]で手軽に自動実装できる
トレイトはRustの型システムの核心です。慣れてくると、安全かつ表現力豊かなコードを書くための強力な武器になります。ぜひ自分でトレイトを定義して、いろいろな型に実装してみてください!