- 技術 💻
trait(トレイト)とは|Rust・PHPの使い方・違い表・エラー原文19種
結論
トレイト(trait)とは、「この型(クラス)はこういう振る舞いができる」というメソッドの組を定義し、複数の型やクラスで共有するための仕組みです。Rust では型に共通の振る舞いを約束させる道具、PHP では継承を使わずにメソッドを差し込む部品として働きます。
この記事でわかること
- Rust・PHP・Scala でのトレイトの意味と、インターフェース・抽象クラス・Mixin との違い
- Rust 1.97 と PHP 8.3 で動かした定義・境界・dyn・insteadof の実行結果と速度の実測
- E0277・E0038 や PHP の Fatal error など、エラー原文19種と直し方
トレイトは「振る舞いの約束」と「その中身の一部」を、型やクラスの外に切り出したものです。Rust と PHP では同じ名前でも役割が少し違うので、全体を図にしました。
flowchart TD
T[trait トレイト<br>メソッドの組を定義] --> R[Rust<br>型が守る約束]
T --> P[PHP<br>クラスに差し込む部品]
R --> G[T: Shape 静的<br>dyn Shape 動的]
P --> U[use で取り込む<br>insteadof で解決]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| trait(トレイト)とは | 振る舞い(メソッドの組)を定義し、複数の型で共有する仕組み | トレイト(trait)とは |
| trait の英語の意味 | 「特性・特徴」。プログラミングでは「型が持つ性質」の意味で使う | トレイト(trait)とは |
| インターフェースとの違い | 中身(実装)も持てる。Rust では後から既存の型にも付けられる | インターフェース・抽象クラス・Mixinとの違い |
| rust trait の書き方 | trait 名前 { fn …; } を定義し impl 名前 for 型 |
Rustのトレイトの使い方 |
| impl Trait と dyn Trait の違い | impl は型が1つに決まる(静的)、dyn は実行時に呼び分ける(動的) | dyn Trait(トレイトオブジェクト) |
| php trait の使い方 | trait 名前 { … } を定義し、クラスの中で use 名前; |
PHPのトレイト(trait)の使い方 |
| PHP で同じ名前のメソッドがぶつかる | insteadof でどちらを使うか選び、as で別名を付ける |
PHP traitの衝突解決 |
| E0277 が出た | その型にトレイトが実装されていない。impl を書くか境界を見直す | Rustのトレイトのエラー |
確認環境:Ubuntu 24.04.5 LTS(x86_64・2コア)の上で、rustup の stable(rustc 1.97.0・cargo 1.97.0)で cargo new から実行しました(edition は既定の 2024)。エラーの原文は rustc --edition 2021 で取り、2018・2024 との違いも確かめています。静的・動的ディスパッチの速さは cargo build --release で作った実行ファイルで、100万要素×20回の合計を5回測った最小値です。PHP は PHP 8.3.6(CLI)で実行し、Java の default メソッドとの比較は OpenJDK 21.0.12 の javac で確かめました。Scala はこの環境に無いため、Scala 公式ドキュメント(Scala 3 Book・Tour of Scala)で確かめた範囲だけを書いています。トレイトの定義と規則は The Rust Programming Language(日本語版)・The Rust Reference・Rust By Example・PHP マニュアル(日本語版)で確かめました。図は mermaid で描画を確かめています(確認日 2026年10月29日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「trait(トレイト)とは|Rust・PHPの使い方・違い表・エラー原文19種」
トレイト(trait)とは(意味と考え方)
この節では、trait という言葉の意味と、プログラミングでのトレイトが何をするものかが分かります。
trait は英語で「特性・特徴」という意味です。プログラミングでは、「この型はこういう振る舞いができる」という性質を、メソッドの組として書き出したものをトレイトと呼びます。
たとえば「面積を計算できる」という性質を Shape というトレイトにすると、円にも長方形にも同じ「面積を計算できる」という約束を付けられます。円と長方形は親子関係(継承)ではありませんが、同じトレイトを持つことで、同じ関数に渡したり、同じ一覧に並べたりできるようになります。
言語によって、トレイトの役割の重心が違います。
- Rust:The Rust Programming Language(日本語版)は「トレイトは、特定の型に存在し、他の型と共有できる機能を定義します」と説明しています。Rust にはクラスの継承が無いため、共通の振る舞いはトレイトで表します。ジェネリクスの条件(トレイト境界)にも使います。
- PHP:PHP マニュアルは、トレイトを「単一継承の言語でコードを再利用するための仕組み」と説明しています。継承の階層と関係なく、複数のクラスにメソッドとプロパティの組を差し込む部品です。PHP 5.4 で入りました。
- Scala:Scala 3 の公式ドキュメントは、トレイトを Java 8 以降のインターフェースに近いものとし、抽象メンバーと実装を持つメンバーの両方を持てる、としています。クラスは複数のトレイトを混ぜ込めます。
考え方の元になったのは、Schärli らが 2003 年に Squeak Smalltalk 向けに発表した研究で、「メソッドの組を合成の単位にし、名前がぶつかったら使う側が明示的に選ぶ」という設計です。PHP の insteadof は、この考え方をそのまま文法にしたものです。
約束を決める
「このメソッドを持っていること」を型やクラスに求める。インターフェースと同じ役割。
Rust・PHP・Scala
中身を配る
デフォルト実装やメソッド本体を書いておき、使う側はそれをそのまま使える。
Rust・PHP・Scala
型の条件にする
「Shape を実装した型なら何でも受け取る」というジェネリクスの条件に使う。
Rust(トレイト境界)
型の名前にはならない
PHP のトレイトは型として使えず、instanceof も false になる。手元で確かめた。
PHP
トレイトとインターフェース・抽象クラス・Mixinの違い(比較表)
この節では、トレイトがインターフェース・抽象クラス・Mixin とどう違うのかが、一覧で分かります。
インターフェースは「約束だけ」、抽象クラスは「約束と中身と状態を持つ親」、トレイトは「約束と中身を、継承と関係なく配る部品」と覚えると整理しやすくなります。
| 比べる点 | インターフェース | 抽象クラス | トレイト |
|---|---|---|---|
| 中身(実装)を書ける | 言語による(Java 8 以降は default メソッド) | 書ける | 書ける(Rust はデフォルト実装) |
| 状態(フィールド)を持てる | 持てない(定数のみ) | 持てる | PHP・Scala は持てる。Rust は持てない |
| 1つのクラスにいくつ付けられる | いくつでも | 1つ(単一継承) | いくつでも |
| 型として使える | 使える | 使える | Rust は dyn・impl として、Scala は型として使える。PHP は使えない |
| 名前がぶつかったとき | Java はコンパイルエラーで、自分で上書きする | (単一継承なので起きにくい) | PHP は insteadof、Rust は呼び出し側で指定 |
| 既にある型に後から付ける | できない(宣言時に implements) | できない | Rust はできる(自作トレイトを i32 などに実装) |
Mixin との違い
Mixin(ミックスイン)は、クラスに機能を「混ぜ込む」仕組みの総称で、Ruby のモジュールの include などがこれにあたります。英語版 Wikipedia の「Trait (computer programming)」は、Mixin は混ぜる順番で動きが変わることがあるのに対し、トレイトは順番が結果に影響せず、名前の衝突は使う側が明示的に解決する点が違う、と説明しています。
Java の default メソッドとの比較(実行結果)
Java のインターフェースも、Java 8 から default メソッドで中身を持てるようになり、トレイトに近づきました。同じ名前の default メソッドを持つ2つのインターフェースを実装すると、次のエラーになりました。
$ javac Talker.java
Talker.java:3: error: types A and B are incompatible;
public class Talker implements A, B {
^
class Talker inherits unrelated defaults for talk() from types A and B
1 error
Java では、クラスで talk() を上書きし、B.super.talk() のように呼び分けると通り、B+A と表示されました。PHP の insteadof と同じ問題を、別の文法で解いています。インターフェースと抽象クラスの使い分けは関連記事「Javaインターフェースと抽象クラスの違い|比較表と使い分け」、default メソッドの衝突は関連記事「Java interfaceのdefaultメソッドと多重継承の衝突」で扱っています。
継承とトレイトの考え方の違い
見分ける問い:「〜の一種」か「〜ができる」か
Rustのトレイトの使い方(定義・impl・デフォルト実装)
この節では、Rust でトレイトを定義し、型に実装し、デフォルト実装を上書きするまでの書き方と実行結果が分かります。
Rust では、trait でメソッドの形(シグネチャ)を並べ、impl トレイト名 for 型名 で型ごとの中身を書きます。 本体を書いたメソッドはデフォルト実装になり、実装側で書かなければそれが使われます。
trait Shape {
fn name(&self) -> String;
fn area(&self) -> f64;
// デフォルト実装:実装側で書かなければこれが使われる
fn describe(&self) -> String {
format!("{} の面積は {:.2}", self.name(), self.area())
}
}
struct Circle { r: f64 }
struct Rect { w: f64, h: f64 }
impl Shape for Circle {
fn name(&self) -> String { "円".to_string() }
fn area(&self) -> f64 { 3.14159 * self.r * self.r }
}
impl Shape for Rect {
fn name(&self) -> String { "長方形".to_string() }
fn area(&self) -> f64 { self.w * self.h }
// デフォルト実装を上書き
fn describe(&self) -> String {
format!("{}×{} の長方形(面積 {})", self.w, self.h, self.area())
}
}
cargo run の出力です。Circle はデフォルト実装の describe、Rect は上書きした describe が呼ばれました。
--- 1. 定義・impl・デフォルト実装
円 の面積は 12.57
3×4 の長方形(面積 12)
trait で約束を書く
メソッドの名前・引数・戻り値を並べる。&self を取るとメソッドになり、取らないと Shape::new() のような関連関数になる。
デフォルト実装を付ける(任意)
本体を書いたメソッドは、実装側で省略できる。デフォルト実装の中から、まだ中身の無い他のメソッドを呼んでもよい。
impl で型ごとの中身を書く
本体の無いメソッドは全部書く。1つでも欠けると E0046 になる。トレイトに無いメソッドを書くと E0407 になる。
使う側でトレイトを読み込む
トレイトのメソッドは、そのトレイトがスコープにあるときだけ呼べる。別のモジュールにあるなら use で読み込む(無いと E0599)。
型だけに付けたいメソッドは、トレイトではなく impl Circle { … } に書きます。Rust の全体像は、第27弾の記事「Rust言語とは?特徴・できること・入門|所有権エラーと速度を実測」で扱っています。
Rustのトレイト境界(ジェネリクス・impl Trait・where句)
この節では、「Shape を実装した型なら何でも受け取る」関数の3つの書き方と、どれを選ぶかが分かります。
トレイト境界(trait bound)は、ジェネリクスの型に「このトレイトを実装していること」という条件を付ける書き方です。 次の3つは、どれも Shape を実装した型を受け取ります。
use std::fmt;
// 1. 引数に impl Trait(いちばん短い)
fn print_a(s: &impl Shape) { println!("impl Trait : {}", s.describe()); }
// 2. 型引数に境界を付ける
fn print_b<T: Shape>(s: &T) { println!("T: Shape : {}", s.describe()); }
// 3. where 句(境界が長いとき・複数のとき)
fn print_c<T>(s: &T) where T: Shape + fmt::Debug { println!("where 句 : {:?}", s); }
--- 2. 引数としてのトレイト
impl Trait : 円 の面積は 12.57
T: Shape : 3×4 の長方形(面積 12)
where 句 : Circle { r: 2 }
| 書き方 | 向いている場面 | 注意 |
|---|---|---|
&impl Shape |
引数が1つで、条件も1つ | 呼び出し側で型を ::<T> と指定できない |
<T: Shape> |
2つの引数を「同じ型」にそろえたい | fn f<T: Shape>(a: &T, b: &T) は a と b が同じ型 |
where T: A + B |
条件が複数・型引数が複数 | 関数名の行が短くなり読みやすい |
-> impl Iterator<Item = u32> |
戻り値の型を書きたくない(クロージャやイテレーター) | 返せる型は1種類だけ(違う型を返すと E0308) |
戻り値の impl Trait は、長いイテレーターの型を書かずに返すときに便利です。手元の fn evens(limit: u32) -> impl Iterator<Item = u32> は [0, 2, 4, 6, 8] を返しました。ただし、if で円と長方形のどちらかを返すような「型が実行時に決まる」場合は使えず、次の節の dyn を使います。
ジェネリクスとトレイト境界を使った関数は、コンパイル時に型ごとの関数が作られます(単相化)。 呼び出しのたびに中身を探す手間が無いため速く、その代わり型の数だけ機械語が増えます。TypeScript や Go の似た仕組みは、関連記事「Go interfaceの使い方|型アサーション・型スイッチ・anyを図解」で扱っています。
dyn Trait(トレイトオブジェクト)と静的・動的ディスパッチの速さ
この節では、dyn Trait が何をしているのかと、ジェネリクス・enum と比べたときの大きさと速さの実測が分かります。
dyn Shape は「Shape を実装した何かの型」を、実行時に呼び分けるための型です。 円と長方形を1つの Vec に入れるときに使います。
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}
let shapes: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { r: 1.0 }),
Box::new(Rect { w: 2.0, h: 5.0 }),
];
for s in &shapes { println!("{}", s.describe()); }
println!("合計 {:.5}", total_area(&shapes));
println!("size_of &Circle = {} バイト", size_of::<&Circle>());
println!("size_of &dyn Shape = {} バイト", size_of::<&dyn Shape>());
println!("size_of Box<dyn Shape> = {} バイト", size_of::<Box<dyn Shape>>());
--- 3. dyn Trait
円 の面積は 3.14
2×5 の長方形(面積 10)
合計 13.14159
size_of &Circle = 8 バイト
size_of &dyn Shape = 16 バイト
size_of Box<dyn Shape> = 16 バイト
普通の参照は8バイトなのに、&dyn Shape は16バイトでした。 「データの場所」と「その型のメソッド表(vtable)の場所」の2つを持つ太いポインターだからです。呼ぶときは、メソッド表から area の場所を引いてから呼びます。
ジェネリクス・dyn・enum の速さ(実測)
同じ「面積の合計」を、ジェネリクス(静的ディスパッチ)、Box<dyn Shape>、&dyn Shape、enum の match の4通りで、100万要素×20回計算しました(--release・5回の最小値)。
要素数 1000000 × 20 回、5試行の最小
ジェネリクス(静的)Vec<Circle> 25.98 ms (1.30 ns/回) check=40840629
dyn(同じ型だけ)Vec<Box<dyn>> 112.52 ms (5.63 ns/回) check=40840629
dyn(2種類が交互)Vec<Box<dyn>> 115.81 ms (5.79 ns/回) check=23420342
&dyn(連続した配列を指す) 106.87 ms (5.34 ns/回) check=40840629
enum(2種類が交互)Vec<enum> 38.68 ms (1.93 ns/回) check=23420342
size_of Circle=8 Rect=16 AnyShape=24 Box<dyn Shape>=16
- この環境では、dyn はジェネリクスの約4〜6倍の時間がかかりました。 共有の環境のため測るたびに値がぶれ、8回の計測でジェネリクスは 0.82〜1.66ns、Box<dyn> は 3.9〜10.2ns の範囲でした。上の出力はそのうちの1回です。
- 連続した配列を指す
&dynでも遅かったので、差の主な理由は「ヒープに散らばったデータ」ではなく、メソッド表を経由する呼び出しで、コンパイラが中身を展開(インライン化)できないことだと読めます。 - 型の種類が決まっているなら、enum の
matchは dyn より速く、ジェネリクスに近い値でした。数ナノ秒の差なので、通信を含む処理では見えないことがほとんどで、速さより「型が実行時に決まるか」で選びます。
ジェネリクス・impl Trait(静的)
型はコンパイル時に1つに決まる
速い。型ごとに機械語が作られる
Vec に違う型を混ぜられない
dyn Trait(動的)
型は実行時に決まる
メソッド表を引く分だけ遅い
違う型を1つの Vec に入れられる
dyn にできないトレイト(dyn compatibility)
すべてのトレイトを dyn にできるわけではありません。 The Rust Reference は、dyn にできる条件(dyn compatibility。以前は object safety と呼ばれていました)として、メソッドがレシーバー以外で Self を使わないこと、型引数を持たないこと、関連定数を持たないこと、Sized をスーパートレイトにしないこと、などを挙げています。fn dup(&self) -> Self を持つトレイトを dyn にすると E0038 になりました(原文はエラーの節)。
Reference のとおり、そのメソッドに where Self: Sized を付けると、トレイト全体は dyn にでき、そのメソッドだけ dyn からは呼べなくなります。手元でも、付けたあとは Vec<Box<dyn Duplicate>> を作れ、具体的な型からは dup() を呼べました。
trait Duplicate {
fn dup(&self) -> Self where Self: Sized; // dyn からは呼べなくなる
fn label(&self) -> &'static str;
}
標準ライブラリのトレイト(Display・Add・derive・Iterator・スーパートレイト)
この節では、Rust の標準ライブラリにあるトレイトを自分の型に実装すると何ができるようになるかと、関連型・スーパートレイト・ブランケット実装が分かります。
Rust の println!("{}")・+ 演算子・==・for ループは、どれもトレイトで動いています。 自分の型にトレイトを実装すると、組み込みの型と同じように使えるようになります。
#[derive(Debug, Clone, Copy, PartialEq)] // 4つのトレイトを自動で実装
struct Point { x: i32, y: i32 }
impl fmt::Display for Point { // println!("{}") で表示できる
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl Add for Point { // + で足せる
type Output = Point; // 関連型:結果の型
fn add(self, o: Point) -> Point { Point { x: self.x + o.x, y: self.y + o.y } }
}
struct Countdown(u32);
impl Iterator for Countdown { // for・collect・sum が使える
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.0 == 0 { None } else { self.0 -= 1; Some(self.0 + 1) }
}
}
--- 4. 関連型 Iterator
[3, 2, 1] sum=55
--- 5. Display・Add・derive
(11, 22) / Point { x: 11, y: 22 } / to_string="(11, 22)" / eq=true
- Display を実装しただけで
to_string()も使えました。 標準ライブラリに「Display を実装した全部の型に ToString を実装する」というブランケット実装があるためです。 Iteratorはnextを1つ書くだけで、collect・sum・map・filterなどのデフォルト実装がすべて使えます。デフォルト実装のいちばん大きな例です。type Item = u32;のような関連型は、「このトレイトで使う型」を実装側が1つに決めるものです。ジェネリクスのIterator<T>にすると、1つの型に何通りも実装できてしまい、next()の結果の型が決まらなくなります。
スーパートレイトとブランケット実装
// スーパートレイト:Greeter を実装するには Named も必要
trait Named { fn id(&self) -> String; }
trait Greeter: Named {
fn greet(&self) -> String { format!("こんにちは、{} です", self.id()) }
}
struct Bot;
impl Named for Bot { fn id(&self) -> String { "bot-01".into() } }
impl Greeter for Bot {}
// ブランケット実装:Shape を実装した全部の型に Report を付ける
trait Report { fn report(&self) -> String; }
impl<T: Shape> Report for T {
fn report(&self) -> String { format!("[report] {}", self.describe()) }
}
--- 6. スーパートレイト
こんにちは、bot-01 です
--- 7. ブランケット実装
[report] 3×4 の長方形(面積 12)
ブランケット実装は便利ですが、同じ型に別の impl Report を書くと E0119(実装の衝突)になります。手元でも、impl Report for Circle を足すと E0119 でした。
| トレイト | 実装するとできること | derive できるか |
|---|---|---|
| Debug | {:?} で表示 |
できる |
| Display | {} で表示・to_string() |
できない(自分で書く) |
| Clone・Copy | .clone()・代入でコピー |
できる |
| PartialEq・Eq | == で比べる |
できる |
| PartialOrd・Ord | <・sort() |
できる |
| Hash | HashMap のキーにする | できる |
| Add・Sub など | +・- の演算子 |
できない |
| Iterator | for・map・collect |
できない |
| Drop | 値が捨てられるときの後始末 | できない |
孤児ルールと同名メソッドの呼び分け(Rust)
ここでは、Rust で「どの型にどのトレイトを実装してよいか」の決まりと、同じ名前のメソッドが複数あるときの呼び方が分かります。
Rust では、「トレイト」か「型」の少なくとも一方が自分のクレート(パッケージ)のものでなければ、実装できません。 これを孤児ルール(orphan rule)と呼びます。The Rust Programming Language(日本語版)は、この規則により「他の人のコードが自分のコードを壊したり、その逆が起きないことを保証してくれます」と説明しています。
| 実装しようとしたもの | 結果 |
|---|---|
| 自作の Shape を自作の Circle に | できる |
| 標準の Display を自作の Point に | できる(型が自作) |
| 自作の Report を標準の i32・Vec に | できる(トレイトが自作) |
| 標準の Display を標準の Vec<i32> に | E0117 でできない |
外部の型に外部のトレイトを付けたいときは、エラーの原文にあるとおり「新しい型(newtype)」で包みます。struct MyList(Vec<i32>); を作り、impl fmt::Display for MyList と書けば通ります。
同じ名前のメソッドを呼び分ける
型が、同じ名前のメソッドを持つ2つのトレイトを実装していても、Rust ではエラーになりません。呼び出し側でどちらかを指定します。
trait Pilot { fn fly(&self) -> &'static str; }
trait Wizard { fn fly(&self) -> &'static str; }
struct Human;
impl Pilot for Human { fn fly(&self) -> &'static str { "機長です" } }
impl Wizard for Human { fn fly(&self) -> &'static str { "空を飛ぶ魔法" } }
impl Human { fn fly(&self) -> &'static str { "腕を振る" } }
let h = Human;
println!("{} / {} / {}", h.fly(), Pilot::fly(&h), <Human as Wizard>::fly(&h));
--- 8. 同名メソッド
腕を振る / 機長です / 空を飛ぶ魔法
h.fly() は型自身のメソッドが優先されました。型自身に fly が無いと、h.fly() は E0034 になり、エラーの文言の中に Pilot::fly(&Human) と書き直す案が出ました。PHP がクラスを定義する時点で衝突を解決させるのに対し、Rust は呼ぶ時点で選ばせる、という違いがあります。
Rustのトレイトのエラー原文と直し方(E0277・E0038 など10種)
この節では、Rust でトレイトを使うとよく出るコンパイルエラーの原文と、それぞれの直し方が分かります。
トレイトのエラーは、「実装が無い(E0277・E0599)」「実装が足りない・多い(E0046・E0407)」「実装してはいけない(E0117・E0119)」「dyn の使い方(E0038・E0782・E0308)」「呼び分け(E0034)」に分かれます。 いちばんよく出る E0277 の原文です。
error[E0277]: the trait bound `Triangle: Shape` is not satisfied
--> e0277.rs:6:56
|
6 | fn main() { print_area(&Circle { r: 1.0 }); print_area(&Triangle); }
| ---------- ^^^^^^^^^ unsatisfied trait bound
| |
| required by a bound introduced by this call
|
help: the trait `Shape` is not implemented for `Triangle`
--> e0277.rs:4:1
|
4 | struct Triangle;
| ^^^^^^^^^^^^^^^
help: the trait `Shape` is implemented for `Circle`
note: required by a bound in `print_area`
--> e0277.rs:5:18
|
5 | fn print_area<T: Shape>(s: &T) { println!("{}", s.area()); }
| ^^^^^ required by this bound in `print_area`
原文の help に「Triangle には Shape が実装されていない」「Circle には実装されている」、note に「print_area の境界で必要」と、原因と場所が両方出ています。dyn にできないトレイトの E0038 は次のとおりです。
error[E0038]: the trait `Duplicate` is not dyn compatible
--> e0038.rs:4:32
|
4 | fn main() { let v: Vec<Box<dyn Duplicate>> = vec![Box::new(A)]; }
| ^^^^^^^^^ `Duplicate` is not dyn compatible
|
note: for a trait to be dyn compatible it needs to allow building a vtable
for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>
--> e0038.rs:1:36
|
1 | trait Duplicate { fn dup(&self) -> Self; }
| --------- ^^^^ ...because method `dup` references the `Self` type in its return type
| |
| this trait is not dyn compatible...
= help: consider moving `dup` to another trait
10種のエラーの1行目と直し方の一覧です(rustc 1.97.0・edition 2021)。
| コード | エラー原文(1行目) | 起こした操作 | 直し方 |
|---|---|---|---|
| E0277 | the trait bound `Triangle: Shape` is not satisfied | 境界 T: Shape の関数に未実装の型を渡した |
impl Shape for Triangle を書く |
| E0046 | not all trait items implemented, missing: `area` | impl で area を書き忘れた |
本体の無いメソッドを全部書く |
| E0407 | method `perimeter` is not a member of trait `Shape` | トレイトに無いメソッドを impl に書いた | impl Circle { … } に移す |
| E0599 | no method named `area` found for struct `Circle` in the current scope | トレイトを use していない |
use crate::shapes::Shape; |
| E0117 | only traits defined in the current crate can be implemented for types defined outside of the crate | Display を Vec<i32> に実装 | newtype で包む |
| E0119 | conflicting implementations of trait `Report` for type `Circle` | ブランケット実装と個別の実装が重なった | どちらかを消す・境界を絞る |
| E0038 | the trait `Duplicate` is not dyn compatible | -> Self のメソッドを持つトレイトを dyn にした |
where Self: Sized を付ける・別のトレイトに分ける |
| E0782 | expected a type, found a trait | Box<Shape> と dyn を付けずに書いた |
Box<dyn Shape> |
| E0308 | `if` and `else` have incompatible types | -> impl Shape で円と長方形を返し分けた |
-> Box<dyn Shape> にする |
| E0034 | multiple applicable items in scope | 同名メソッドを持つ2つのトレイト | Pilot::fly(&h) と書く |
- E0782 は edition で扱いが違いました。
--edition 2018では「trait objects without an explicit `dyn` are deprecated」という警告で通り、2021 と 2024 ではエラーでした。以前の版のコードや記事のBox<Shape>は、dynを足して読み替えます。 - E0308 では、rustc が「戻り値を
Box<dyn Shape>にし、Box::newで包む」という直し方をそのまま示しました。 - どのエラーも
rustc --explain E0277で、例つきの説明を読めます。
PHPのトレイト(trait)の使い方(use・優先順位)
この節では、PHP でトレイトを定義してクラスに取り込む書き方と、クラス・トレイト・親クラスに同じメソッドがあるときにどれが呼ばれるかが分かります。
PHP では trait でメソッドとプロパティの組を定義し、クラスの中で use トレイト名; と書いて取り込みます。 取り込んだメソッドは、クラスに直接書いたメソッドと同じように呼べます。
<?php
declare(strict_types=1);
trait Timestampable {
private ?string $createdAt = null;
public function touch(string $at): static { $this->createdAt = $at; return $this; }
public function createdAt(): ?string { return $this->createdAt; }
}
trait Loggable {
public function log(string $msg): string { return '[' . static::class . '] ' . $msg; }
}
class User { use Timestampable, Loggable; public function __construct(public string $name) {} }
class Invoice { use Timestampable, Loggable; }
$u = (new User('alice'))->touch('2026-10-29 10:00');
echo $u->createdAt(), "\n";
echo $u->log('ログイン'), "\n";
echo (new Invoice)->log('発行'), "\n";
$ php basic.php
--- 1. use
2026-10-29 10:00
[User] ログイン
[Invoice] 発行
User と Invoice は親子関係ではありませんが、同じ「時刻を記録する」「ログを書く」部品を持ちました。トレイトの中の static::class は、取り込んだクラスの名前(User・Invoice)になりました。クラスの書き方の基本は、関連記事「PHPクラスの書き方|コンストラクタ・static・継承をPHP8対応で解説」で扱っています。
優先順位:クラス > トレイト > 親クラス
PHP マニュアルは「優先順位は現在のクラスのメンバーが最高で、その次がトレイトのメソッド、そしてその次にくるのが継承したメソッド」と書いています。3か所に同じ名前のメソッドを置いて確かめました。
class Base { public function hello(): string { return 'Base'; } public function bye(): string { return 'Base::bye'; } }
trait SayHello {
public function hello(): string { return 'Trait(親は ' . parent::hello() . ')'; }
public function bye(): string { return 'Trait::bye'; }
}
class Child extends Base { use SayHello; public function bye(): string { return 'Child::bye'; } }
$c = new Child;
echo $c->hello(), "\n", $c->bye(), "\n";
--- 2. 優先順位
Trait(親は Base)
Child::bye
hello()は親クラスよりトレイトが優先され、トレイトの中のparent::hello()で親のメソッドも呼べました。bye()はクラスに直接書いたものがトレイトより優先されました。- PHP マニュアルには、PHP 8.5.0 でトレイトが親クラスより先にクラスに結び付けられるように変わったが、メソッドの解決はこの変更の影響を受けない、とあります。
PHP traitの衝突解決(insteadof・as)とabstract・static・定数
この節では、2つのトレイトのメソッドがぶつかったときの解決方法と、トレイトに抽象メソッド・static・定数を書いたときの動きが分かります。
同じ名前のメソッドを持つトレイトを2つ取り込むと、Fatal error になります。insteadof で「どちらを使うか」を選び、as で使わなかった方に別名を付けます。
trait A { public function talk(): string { return 'A'; } }
trait B { public function talk(): string { return 'B'; } }
class Talker {
use A, B {
B::talk insteadof A; // talk は B を使う
A::talk as protected talkA; // A の talk を protected の別名で残す
A::talk as public aliasA; // 公開の別名も付けられる
}
public function both(): string { return $this->talk() . '+' . $this->talkA(); }
}
$t = new Talker;
echo $t->talk(), ' ', $t->both(), ' ', $t->aliasA(), "\n";
--- 3. insteadof / as
B B+A A
PHP マニュアルのとおり、as はメソッドの名前を変えるのではなく、別名を「足す」だけです。talk as protected; のように別名を付けずに書くと、公開範囲だけを変えられます。手元で外から呼ぶと「Call to protected method」のエラーになりました。
abstract・static・定数・トレイトの合成
// 抽象メソッド:使う側のクラスに name() を求める
trait Greets {
abstract public function name(): string;
public function greet(): string { return 'こんにちは、' . $this->name() . ' さん'; }
}
class Customer { use Greets; public function name(): string { return '山田'; } }
// static プロパティは、取り込んだクラスごとに別になる
trait Counter {
private static int $count = 0;
public static function inc(): int { return ++self::$count; }
}
class P1 { use Counter; } class P2 { use Counter; }
P1::inc(); P1::inc();
echo 'P1=', P1::inc(), ' P2=', P2::inc(), "\n";
// 定数(PHP 8.2 から)
trait HasVersion { public const VERSION = '1.2.0'; }
class Api { use HasVersion; }
// トレイトの中でトレイトを使う
trait Hello { public function h(): string { return 'Hello'; } }
trait World { public function w(): string { return 'World'; } }
trait HelloWorld { use Hello, World; public function hw(): string { return $this->h() . ' ' . $this->w(); } }
--- 4. abstract
こんにちは、山田 さん
--- 5. static
P1=3 P2=1
--- 6. 定数
1.2.0
--- 8. トレイトの合成
Hello World
1abstract で前提を書ける
トレイトが使う側に求めるメソッドを abstract で宣言すると、書き忘れたクラスは定義の時点でエラーになる。
2static はクラスごと
同じ Counter を使っても P1 は 3、P2 は 1 だった。static プロパティはトレイトではなく、取り込んだクラスごとに持つ。
3定数は 8.2 から
PHP マニュアルによると、トレイトの定数は 8.2.0 から、as final は 8.3.0 から使える。
インターフェースと組み合わせる(型として使うために)
PHP のトレイトは型ではないため、引数の型や instanceof には使えません。 「型として扱いたい」「中身は共通にしたい」ときは、インターフェースで型を決め、トレイトで中身を配ります。
interface Jsonable { public function toJson(): string; }
trait JsonableImpl {
public function toJson(): string { return json_encode(get_object_vars($this), JSON_UNESCAPED_UNICODE); }
}
class Product implements Jsonable {
use JsonableImpl;
public function __construct(public string $name, public int $price) {}
}
$p = new Product('ノート', 180);
echo $p->toJson(), "\n";
var_dump($p instanceof Jsonable);
var_dump(class_uses($p));
--- 7. interface + trait
{"name":"ノート","price":180}
bool(true)
array(1) {
["JsonableImpl"]=>
string(12) "JsonableImpl"
}
クラスがどのトレイトを使っているかは class_uses() で取れました。ただし class_uses() は親クラスのトレイトを含まないと PHP マニュアルにあるため、継承しているクラスでは親もたどります。トレイトかどうかは trait_exists() が true、class_exists() が false でした。
PHP traitのエラー原文と直し方(Fatal error など9種)
この節では、PHP でトレイトを使ったときに出るエラーの原文と、それぞれの直し方が分かります。
PHP のトレイトのエラーは、ほとんどがクラスを定義する時点の Fatal error です。 PHP 8.3.6 で起こした原文です。
$ php e1.php
PHP Fatal error: Trait method B::talk has not been applied as Talker::talk, because of collision with A::talk in e1.php on line 4
$ php e3.php
PHP Fatal error: User and HasName define the same property ($name) in the composition of User. However, the definition differs and is considered incompatible. Class was composed in e3.php on line 3
$ php e5.php
PHP Fatal error: Uncaught TypeError: write(): Argument #1 ($x) must be of type Loggable, User given, called in e5.php on line 5
| 起こした操作 | エラー原文 | 直し方 |
|---|---|---|
| 同名メソッドの2つのトレイトを use | Trait method B::talk has not been applied as Talker::talk, because of collision with A::talk | B::talk insteadof A; で選ぶ |
new Loggable() |
Cannot instantiate trait Loggable | トレイトを使うクラスを new する |
| トレイトとクラスで初期値の違う同名プロパティ | User and HasName define the same property ($name) in the composition of User. However, the definition differs and is considered incompatible. | どちらか一方にだけ書く |
| トレイトの abstract メソッドを書かない | Class Customer contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Customer::name) | クラスに name() を書く |
| 引数の型にトレイト名を書いた | write(): Argument #1 ($x) must be of type Loggable, User given | インターフェースを作り、その型を書く |
as protected にしたメソッドを外から呼んだ |
Call to protected method T::talk() from global scope | 公開の別名を付ける・クラスの中から呼ぶ |
| トレイトの static メソッドを直接呼んだ | Deprecated: Calling static trait method Counter::inc is deprecated, it should only be called on a class using the trait | P1::inc() のようにクラスから呼ぶ |
as final にしたメソッドを子で上書き |
Cannot override final method Base::check() | 上書きをやめる(意図どおりの防止) |
| 無いトレイトを use(綴り違い・読み込み忘れ) | Trait "Missing" not found | 名前空間の use・オートロードを見直す |
- 引数の型にトレイト名を書いても構文エラーにはならず、呼んだ時点で TypeError になりました。 また
new User instanceof Loggableはエラーにならず false を返しました。どちらも「トレイトは型ではない」ことから起きます。 - static メソッドを直接呼ぶ書き方は PHP 8.1.0 から非推奨(Deprecated)で、手元でも警告のあと実行は続きました。
- Trait "…" not found は、名前空間の付け忘れが多い原因です。トレイトもクラスと同じく名前空間に属し、
use App\Traits\Loggable;で読み込んでからクラスの中でuse Loggable;と書きます。名前空間とオートロードは関連記事「PHP namespaceとuseの使い方|名前空間とPSR-4を図解」で扱っています。
Scalaのトレイト(公式ドキュメントの範囲)
この節では、Scala のトレイトの書き方と、Scala 3 で変わった点が分かります。
Scala のトレイトは、抽象メンバーと実装を持つメンバーを両方持てる、インターフェースと Mixin を合わせたものです。 Scala 3 の公式ドキュメント(Scala 3 Book)で確かめた要点です。Scala はこの環境に入っていないため、ここだけは動かして確かめていません。
- クラスは1つの親クラスと、いくつものトレイトを持てる。Scala 2 では
extends A with B、Scala 3 ではextends A, Bとカンマでも書ける。 - トレイトには、実装の無い抽象メンバー(
def walk(): Unitなど)と、実装のある具象メンバーを並べられる。 - Scala 3 から、トレイトが引数(トレイトパラメーター)を取れるようになった。公式の例は
trait Pet(name: String):をclass Dog(name: String, var age: Int) extends Pet(name):が受け継ぐ形。 - 公式は、多くの場合はクラスや抽象クラスよりトレイトを優先し、インスタンスを作りたい型にクラスを使う、と説明している。
公式の書き方に沿った最小の形です。
trait HasLegs:
def numLegs: Int // 抽象メンバー
def walk(): Unit
def stop() = println("Stopped walking") // 具象メンバー
trait HasTail:
def tailColor: String
def wagTail() = println("Tail is wagging")
class Dog(name: String) extends HasLegs, HasTail:
val numLegs = 4
val tailColor = "Brown"
def walk() = println("I’m walking")
トレイトを使うかどうかの判断表(設計での使いどころ)
この節では、トレイト・インターフェース・継承・enum のどれを選ぶかと、トレイトでテストを書きやすくする方法が分かります。
「〜ができる」という性質を、関係の無い複数の型で共有したいときがトレイトの出番です。 次の順に考えると選びやすくなります。
- 1共有したいのは「〜の一種」か「〜ができる」か「〜の一種」で状態も共有するなら継承、「〜ができる」ならトレイト・インターフェース
- 2中身(実装)まで共有したいか約束だけならインターフェース、中身も配るならトレイト
- 3(PHP)型として受け取りたいか受け取りたいならインターフェースを作り、中身をトレイトで配る
- 4(Rust)型の種類はコンパイル時に決まっているか決まっていればジェネリクス、閉じた種類なら enum、実行時に増えるなら dyn
- 5外から差し替えたいか(テスト・設定)外部とのやり取り(送信・時刻・保存)をトレイトにしておく
| やりたいこと | 向いている書き方 | 理由 |
|---|---|---|
| (PHP)複数のクラスに同じログ・時刻の処理を入れる | トレイト | 親子関係と関係なく差し込める |
| (PHP)引数で「保存できるもの」を受け取る | インターフェース+トレイト | トレイトは型にならない |
| (PHP)状態と処理をまとめた土台を作る | 抽象クラス | コンストラクタと状態を持たせやすい |
| (Rust)いろいろな型を受け取る関数 | ジェネリクス+トレイト境界 | 速く、型の誤りはコンパイル時に分かる |
| (Rust)プラグインのように型が後から増える | Vec<Box<dyn Trait>> |
型が実行時に決まっても扱える |
| (Rust)種類が決まっている(円・長方形・三角形だけ) | enum と match | 漏れをコンパイラが見つけ、dyn より速かった |
(Rust)自分の型を {} で表示・+ で足す |
標準のトレイトを実装 | 組み込みの型と同じ書き方になる |
トレイトでテストを書きやすくする(Rust)
メール送信のように外部とやり取りする処理をトレイトにしておくと、テストでは偽物(フェイク)に差し替えられます。
pub trait Notifier {
fn send(&mut self, to: &str, body: &str) -> Result<(), String>;
}
pub fn remind_all<N: Notifier>(n: &mut N, users: &[&str]) -> usize {
users.iter().filter(|u| n.send(u, "提出期限は明日です").is_ok()).count()
}
#[cfg(test)]
mod tests {
use super::*;
struct FakeNotifier { sent: Vec<String>, fail_for: &'static str }
impl Notifier for FakeNotifier {
fn send(&mut self, to: &str, _body: &str) -> Result<(), String> {
if to == self.fail_for { return Err("送信失敗".into()); }
self.sent.push(to.to_string());
Ok(())
}
}
#[test]
fn counts_only_successful_sends() {
let mut fake = FakeNotifier { sent: vec![], fail_for: "bob" };
assert_eq!(remind_all(&mut fake, &["alice", "bob", "carol"]), 2);
assert_eq!(fake.sent, vec!["alice", "carol"]);
}
}
$ cargo test --lib
running 1 test
test tests::counts_only_successful_sends ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
本物の送信処理は、本番用の型に impl Notifier を書くだけで差し替わります。オブジェクト指向の「部品を差し替えられるように作る」考え方は、関連記事「オブジェクト指向とは?コードでわかる基本と使いどころ」で扱っています。
PHP のトレイトでやってはいけないこと
トレイトのプロパティに頼りすぎると、使う側のクラスとの結び付きが強くなる
よくあるエラー・つまずき(一覧)
この節では、トレイトでつまずきやすい点を、症状・原因・直し方の一覧で確かめられます。
| 症状(原文) | 言語 | 原因 | 直し方 |
|---|---|---|---|
| the trait bound `X: Shape` is not satisfied | Rust | 型にトレイトが実装されていない | impl を書く・derive を足す |
| no method named `area` found … items from traits can only be used if the trait is in scope | Rust | トレイトを use していない | rustc の案のとおり use を足す |
| the trait `X` is not dyn compatible | Rust | Self を返す・ジェネリックなメソッドがある | where Self: Sized・トレイトを分ける |
| expected a type, found a trait | Rust | 以前の版の Box<Trait> の書き方 |
Box<dyn Trait> |
| `if` and `else` have incompatible types | Rust | impl Trait で2種類の型を返した | Box<dyn Trait> か enum |
| has not been applied … because of collision | PHP | 2つのトレイトのメソッド名が同じ | insteadof と as |
| must be of type Loggable, User given | PHP | トレイト名を型に書いた | インターフェースを型にする |
| define the same property … considered incompatible | PHP | トレイトとクラスで同名のプロパティ | 片方にだけ書く |
| types A and B are incompatible | Java | default メソッドが2つのインターフェースでぶつかった | クラスで上書きし A.super.talk() |
よくある質問
この節では、トレイトについてよく調べられている疑問に短く答えます。
Q. trait(トレイト)とは何ですか? A. 「この型はこういう振る舞いができる」という性質を、メソッドの組として定義し、複数の型やクラスで共有する仕組みです。英語の trait は「特性・特徴」という意味です。
Q. トレイトとインターフェースの違いは何ですか? A. インターフェースは主に「約束(メソッドの形)」を決めるもので、トレイトは約束に加えて中身(実装)も配れます。Rust のトレイトは型の条件にもなり、自作のトレイトを既にある型に後から実装できます。PHP のトレイトは型にはならず、中身を差し込む部品です。
Q. トレイトと抽象クラスの違いは何ですか? A. 抽象クラスは「親」として1つだけ継承でき、状態とコンストラクタを持たせやすいものです。トレイトは継承と関係なく、1つのクラスにいくつでも取り込めます。
Q. Rust の impl Trait と dyn Trait はどう使い分けますか? A. 型がコンパイル時に1つに決まるなら impl Trait(またはジェネリクス)、実行時に型が変わるなら dyn Trait です。手元では dyn のほうが1回あたり数ナノ秒遅くなりました。
Q. Rust のトレイトにフィールド(変数)は書けますか?
A. 書けません。トレイトに書けるのはメソッド・関連関数・関連型・関連定数です。値が必要なら fn name(&self) -> &str; のようなメソッドにして、実装側の構造体のフィールドを返します。
Q. Rust で外部クレートの型に外部のトレイトを実装できますか?
A. できません(孤児ルール。E0117)。struct MyList(Vec<i32>); のように自分の型で包む newtype で実装します。
Q. PHP のトレイトはいつから使えますか?
A. PHP 5.4 からです。定数は PHP 8.2.0 から、as final は PHP 8.3.0 から、トレイトの private な抽象メソッドは PHP 8.0.0 から使えると PHP マニュアルにあります。
Q. PHP のトレイトを引数の型に書けますか?
A. 書けません。手元では「must be of type Loggable, User given」の TypeError になり、instanceof も false でした。型として扱いたいときはインターフェースを作り、トレイトの中身と組み合わせます。
Q. PHP で2つのトレイトのメソッド名がぶつかったらどうしますか?
A. use A, B { B::talk insteadof A; A::talk as talkA; } のように、insteadof で使う方を選び、as で他方に別名を付けます。解決しないとクラスの定義時に Fatal error になります。
Q. C++ の「トレイト」は同じものですか?
A. 別のものです。C++ で traits と呼ばれるのは std::numeric_limits や std::iterator_traits のように、テンプレートで型の情報を取り出す技法(型特性)を指すことが多く、Rust・PHP のトレイトとは役割が違います。
まとめ
- トレイト(trait)は「〜ができる」という振る舞いをメソッドの組として定義し、複数の型・クラスで共有する仕組み。インターフェースと違い、中身(実装)も配れる。
- Rust では
traitとimpl トレイト for 型で書き、ジェネリクスの境界(T: Shape・impl Shape・where)にも使う。実行時に型が変わるときはdynで、手元では1回あたり数ナノ秒遅く、参照は16バイトだった。 - Rust のトレイトのエラーは E0277(未実装)・E0599(use 忘れ)・E0038(dyn にできない)・E0117(孤児ルール)など10種を原文で確かめた。rustc の help に直し方が出る。
- PHP では
useで取り込み、優先順位はクラス>トレイト>親クラス。衝突はinsteadofとasで解決し、トレイトは型にならないのでインターフェースと組み合わせる。 - Scala のトレイトは状態を持て型にもなり、Scala 3 では引数も取れる(公式ドキュメントの範囲)。迷ったら「〜の一種か、〜ができるか」で継承とトレイトを分ける。
次に読むとよい記事です。PHP のクラスの基本は関連記事「PHPクラスの書き方|コンストラクタ・static・継承をPHP8対応で解説」で、インターフェースと抽象クラスの使い分けは関連記事「Javaインターフェースと抽象クラスの違い|比較表と使い分け」で扱っています。Rust 自体の始め方は、第27弾の「Rust言語とは?特徴・できること・入門|所有権エラーと速度を実測」で扱っています。
参考・出典
確認日はいずれも 2026年10月29日です。
- The Rust Programming Language 日本語版「トレイト:共通の振る舞いを定義する」:https://doc.rust-jp.rs/book-ja/ch10-02-traits.html
- Rust By Example 日本語版「トレイト」:https://doc.rust-jp.rs/rust-by-example-ja/trait.html
- The Rust Reference「Traits」(Dyn compatibility):https://doc.rust-lang.org/reference/items/traits.html
- The Rust Reference「Implementations」(Orphan rules):https://doc.rust-lang.org/reference/items/implementations.html
- The Rust Edition Guide「Warnings promoted to errors」(Rust 2021):https://doc.rust-lang.org/edition-guide/rust-2021/warnings-promoted-to-error.html
- PHP マニュアル「トレイト」:https://www.php.net/manual/ja/language.oop5.traits.php
- PHP マニュアル「class_uses」:https://www.php.net/manual/ja/function.class-uses.php
- Scala 3 Book「Domain Modeling Tools」(Traits・Trait Parameters):https://docs.scala-lang.org/scala3/book/domain-modeling-tools.html
- Tour of Scala「Traits」:https://docs.scala-lang.org/ja/tour/traits.html
- Tour of Scala「Class Composition with Mixins」:https://docs.scala-lang.org/tour/mixin-class-composition.html
- Wikipedia「Trait (computer programming)」:https://en.wikipedia.org/wiki/Trait_(computer_programming)
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「trait(トレイト)とは|Rust・PHPの使い方・違い表・エラー原文19種」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。