エラーの発生
遅い時間に、きれいなジェネリック関数を書き、cargoがクレートの半分をコンパイルした後、こんなエラーが表示されます:
error[E0277]: the trait bound `T: std::fmt::Display` is not satisfied
--> src/main.rs:4:20
|
4 | println!("{}", value);
| ^^^^^ `T` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `T`
= note: required by a bound in `core::fmt::Display`
help: consider restricting type parameter `T`
|
1 | fn print_value(value: T) {
| ++++++++++++++++++++
コンパイラは珍しく親切で、修正方法まで示してくれています。あとはそれを適用するだけです。
何が起きたのか
型 T に対してジェネリック関数を書き、フォーマット・クローン・比較など、T が特定のトレイトを実装している場合にのみ存在する機能を使おうとしました。Rust はコンパイル時にそれを検証できないため、拒否します。
よくあるトリガー(遭遇頻度の高い順):
println!("{}", value)の使用 —Displayが必要println!("{:?}", value)またはdbg!()の使用 —Debugが必要.clone()の呼び出し —Cloneが必要==または!=の使用 —PartialEqが必要<、>によるソート —PartialOrdが必要
簡単な修正:トレイト境界を追加する
コンパイラの help: 行にすでに追加すべき内容が示されています。型パラメータに直接トレイト境界を付けましょう。
修正前(エラーあり):
fn print_value<T>(value: T) {
println!("{}", value); // E0277: T は Display を実装していない
}
修正後(正常):
fn print_value<T: std::fmt::Display>(value: T) {
println!("{}", value); // コンパイル成功
}
先頭に use インポートを追加すると、シグネチャが読みやすくなります:
use std::fmt::Display;
fn print_value<T: Display>(value: T) {
println!("{}", value);
}
複数の境界:+ を使う
T が複数のトレイトを満たす必要がある場合は、+ でつなぎます:
use std::fmt::{Debug, Display};
fn log_value<T: Display + Debug + Clone>(value: T) {
let copy = value.clone();
println!("display: {}", value);
println!("debug: {:?}", copy);
}
where 句:シグネチャが長くなるとき
複数の型パラメータに複数の境界を重ねると、関数シグネチャが長くなりすぎます。そういう場合は where 句に移動しましょう:
// 読みにくい:
fn compare_and_show<T: PartialOrd + Display, U: Debug + Clone>(a: T, b: U) { ... }
// where を使うとすっきり:
fn compare_and_show<T, U>(a: T, b: U)
where
T: PartialOrd + Display,
U: Debug + Clone,
{
if a > b { println!("{}", a); }
println!("{:?}", b.clone());
}
where 句を使えば、インラインでは書けないもの——関連型への境界や Vec<T>: Display のような構造——も表現できます。
根本的な解決策:よく使うトレイトを derive する
渡す構造体や列挙型を自分で定義している場合、#[derive] が最も簡潔な方法です。手動実装不要で、型が自動的に境界を満たします:
#[derive(Debug, Clone, PartialEq, PartialOrd)]
struct Score {
player: String,
value: u32,
}
fn print_score<T: Debug + Clone>(item: T) {
let copy = item.clone();
println!("{:?}", copy);
}
fn main() {
let s = Score { player: "Alice".into(), value: 42 };
print_score(s); // 動作する — Score は Debug + Clone を derive している
}
自動 derive できるトレイト:
Debug—{:?}フォーマットCloneとCopy— 値の複製PartialEqとEq— 等値チェックPartialOrdとOrd— 順序付けとソートHash— HashMap のキーとして使用Default— ゼロ値または空の値
Display は例外で、手動実装が必要です。カスタム出力フォーマットに共通のデフォルトはないためです。
構造体と impl ブロックにも境界が必要
E0277 は独立した関数だけの問題ではありません。impl ブロックを持つジェネリック構造体では、impl 自体に境界を宣言する必要があります:
struct Wrapper<T> {
inner: T,
}
// 誤り:境界なしで Display を使用している
impl<T> Wrapper<T> {
fn show(&self) {
println!("{}", self.inner); // E0277
}
}
// 正しい:impl ブロックに境界を付ける
impl<T: std::fmt::Display> Wrapper<T> {
fn show(&self) {
println!("{}", self.inner);
}
}
制約を狭く保つために impl ブロックを分割することもできます。境界が必要なメソッドは別の impl<T: Bound> ブロックに入れ、それ以外は通常の impl<T> に残します。制約が明確になり、監査しやすくなります。
境界を追加できないとき:トレイトオブジェクト
型パラメータを制御できない場合——サードパーティの型、混在するコレクション、プラグイン方式の API——では、境界の代わりにトレイトオブジェクト(dyn Trait)を使います:
fn print_any(value: &dyn std::fmt::Display) {
println!("{}", value);
}
fn main() {
print_any(&42);
print_any(&"hello");
print_any(&3.14);
}
トレイトオブジェクトは動的ディスパッチを使います——モノモーフ化されたジェネリクスのゼロコストと比べて、呼び出しごとにわずかな実行時コストがかかります。またオブジェクト安全でないトレイトには使えません。しかしジェネリクスが使えないときは、これで解決できます。
修正の確認
E0277 が解消されたことを確認します:
cargo build 2>&1 | grep E0277
# 何も表示されなければOK
cargo build
# 出力例: Compiling ... / Finished ...
さらに確実にするために、実際の型でコールサイトの境界が保たれているか簡単なテストを追加しましょう:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_print_value() {
print_value(42); // i32 は Display を実装している
print_value("hello"); // &str は Display を実装している
}
}
cargo test
# running 1 test
# test tests::test_print_value ... ok
チートシート
- インライン境界:
fn foo<T: Trait>(x: T)— シンプル、1〜2つのトレイトに - where 句:
fn foo<T>(x: T) where T: Trait— 複数の境界や複雑な型に - Derive:構造体に
#[derive(Debug, Clone)]— 型を自分で定義しているとき - トレイトオブジェクト:
&dyn TraitまたはBox<dyn Trait>— ジェネリクスが使えないとき

