Rustのエラー E0277: the trait bound is not satisfied(トレイト境界を満たしていない)
E0277 は、ある能力が必要な場所に、その能力を持っていない型を使ったという意味のエラーです。表示する、比較する、足し算する、変換する、別のスレッドに送る。どれもトレイト(trait)が必要で、あなたの型にはそのトレイトが足りていません。Rust でとてもよく出るエラーの1つですが、メッセージがたいてい直し方をそのまま教えてくれますし、ほとんどの場合は1行で直せます。(このページはRustのコンパイルエラー解説の一部です。)
エラーメッセージ
Section titled “エラーメッセージ”多くの人が最初に出会うのは、自分で作った構造体を {} で表示しようとしたときです。
error[E0277]: `Player` doesn't implement `std::fmt::Display` --> src/main.rs:8:20 |8 | println!("{}", p); | -- ^ `Player` cannot be formatted with the default formatter | | | required by this formatting parameter |help: the trait `std::fmt::Display` is not implemented for `Player` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead「Player は std::fmt::Display を実装していない」「デフォルトのフォーマッタでは表示できない」と言っています。最後の note は「フォーマット文字列では、代わりに {:?}(整形表示なら {:#?})が使えるかもしれない」という提案です。
このエラーが出るのは、次のプログラムです。
struct Player { name: String, score: u32,}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{}", p);}エラーの意味
Section titled “エラーの意味”トレイトとは、「この型はこれができます」という約束のことです。Display は「自分をきれいに表示する方法を知っています」という約束です。PartialEq は「== で比較できます」、Send は「別のスレッドに移しても安全です」という約束です。
println!、.sort()、+、thread::spawn といったコードは、こうした約束のどれかを求めます。コンパイラは、あなたが渡した値(8行目の p)を指して、「この型はその約束をしていないので、ここでは使えません」と言っているのです。
コンセントを思い浮かべてみてください。コンセントは決まった形のプラグしか受け付けません。電化製品そのものは完璧に動くとしても、プラグの形が合わなければ差し込めません。E0277 は、コンパイラが「プラグの形が違います」と言っているようなものです。直すには、型に正しいプラグを付けてあげます。たいていは #[derive(...)] で、ときどき短い impl ブロックを書きます。
Rust がこうする理由
Section titled “Rust がこうする理由”Rust は推測をしません。あなたが作った Player 型には、表示や比較、並べ替えの「正しいやり方」が1つに決まっているわけではありません。2人のプレイヤーは、名前が同じなら等しいのでしょうか?それとも、すべてのフィールドが同じときだけでしょうか?Rust は、あなたの意図とは違うことを黙ってするのではなく、あなた自身に決めてもらいます。
このチェックはコンパイル時に行われます。多くの言語では、表示のしかたを教えていないオブジェクトを表示すると <Player object at 0x7f...> のような役に立たない文字列が出てきますし、スレッドセーフでない値をスレッドに渡すと、本番環境でまれにクラッシュが起こります。Rust では、どちらもコンパイルエラーになるので、一度直せばそれで終わりです。
トレイトのしくみはトレイトのページで、毎日のように使うトレイト(Debug、Display、PartialEq、Ord、Hash)はよく使うトレイトで詳しく説明しています。
E0277 にはいろいろな顔があります。下から、自分のメッセージに合うものを探してください。
直し方1:構造体を {} や {:?} で表示する
Section titled “直し方1:構造体を {} や {:?} で表示する”{:?} には Debug が必要です。これを手で書くことはほとんどありません。#[derive(Debug)] を付けるだけです。デバッグやログにはこちらを使います。{:?} のときのエラーは、次のようになります。
error[E0277]: `Player` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `Player` = note: add `#[derive(Debug)]` to `Player` or manually `impl Debug for Player`note は「Player に #[derive(Debug)] を付けるか、impl Debug for Player を手で書いてください」と言っています。
{} には Display が必要です。こちらは derive できません。「きれいな」表示がどうあるべきかは、あなたにしかわからないからです。write! を使った小さな impl を書きましょう。ユーザーに見せるテキストにはこちらを使います。
use std::fmt;
#[derive(Debug)] // これだけで {:?} が使えるstruct Player { name: String, score: u32,}
// Display は自分で書く:{} で何を表示するかをここで決めるimpl fmt::Display for Player { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}({}点)", self.name, self.score) }}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{:?}", p); println!("{}", p);}実行結果:
Player { name: "Ana", score: 42 }Ana(42点)直し方2:比較する、並べ替える、HashMap のキーにする
Section titled “直し方2:比較する、並べ替える、HashMap のキーにする”.contains() や == には PartialEq が必要です。.sort() には Ord が必要です。HashSet の要素や HashMap のキーには Eq と Hash が必要です。メッセージは次のようになります。
error[E0277]: can't compare `Point` with `Point` = help: the trait `PartialEq` is not implemented for `Point`
error[E0277]: the trait bound `Priority: Ord` is not satisfied = help: the trait `Ord` is not implemented for `Priority`
error[E0277]: the trait bound `Point: Hash` is not satisfied = help: the trait `Hash` is not implemented for `Point`上から「Point どうしを比較できない」「Priority: Ord というトレイト境界を満たしていない」「Point: Hash というトレイト境界を満たしていない」という意味です。
直し方は、derive することです。これらのトレイトは互いの上に成り立っているので、よく使うフルセットは PartialEq, Eq, Hash, PartialOrd, Ord です。
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]enum Priority { Low, Medium, High,}
fn main() { let mut tasks = vec![Priority::High, Priority::Low, Priority::Medium]; tasks.sort(); // Ord が必要 println!("{:?}", tasks); println!("Highを含む: {}", tasks.contains(&Priority::High)); // PartialEq が必要
let mut counts: HashMap<Priority, u32> = HashMap::new(); // キーには Eq + Hash が必要 for task in &tasks { *counts.entry(*task).or_insert(0) += 1; } println!("Lowのタスク数: {}", counts[&Priority::Low]);}実行結果:
[Low, Medium, High]Highを含む: trueLowのタスク数: 1derive した Ord は、列挙型なら書いた順にバリアントを並べ、構造体ならフィールドを上から順に比べます。ひとつ正直に言っておくと、f64 のフィールドを持つ型は Eq、Hash、Ord を derive できません。浮動小数点数には、自分自身とも等しくならない NaN があるからです。小数を並べ替える方法は並べ替えを見てください。
直し方3:違う数値型を混ぜる(cannot add f64 to i32)
Section titled “直し方3:違う数値型を混ぜる(cannot add f64 to i32)”error[E0277]: cannot add `f64` to `i32` --> src/main.rs:4:23 |4 | let total = items + price; | ^ no implementation for `i32 + f64` | = help: the trait `Add<f64>` is not implemented for `i32`「i32 に f64 を足せない」「i32 + f64 の実装がない」と言っています。
Rust は、数値の型を勝手に変換しません。i32 から f64 への変換でさえもです。+、-、*、/ の両側は同じ型でなければなりません。as を使って、片方を自分で変換しましょう。
fn main() { let items: i32 = 3; let price: f64 = 2.5; let total = items as f64 * price; // 先に変換してから掛ける println!("合計: {total}");}実行結果:
合計: 7.5f64::from(items) でも動き、こちらはデータが失われることが絶対にありません。逆向き(f64 から i32)は小数点以下が切り捨てられるので、先に四捨五入などを考えましょう。詳しくは型キャストと型の変換のページで説明しています。
直し方4:? がエラーを変換できない
Section titled “直し方4:? がエラーを変換できない”error[E0277]: `?` couldn't convert the error to `ConfigError` |8 | fn parse_port(text: &str) -> Result<u16, ConfigError> { | ------------------------ expected `ConfigError` because of this9 | let port = text.parse::<u16>()?; | --------------^ the trait `From<ParseIntError>` is not implemented for `ConfigError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait「? がエラーを ConfigError に変換できなかった」「ConfigError には From<ParseIntError> が実装されていない」と言っています。note は「? 演算子は、From トレイトを使ってエラーの値を暗黙に変換する」という説明です。
? はエラーに出会うと、それを関数から返します。そのエラーの型が、関数が返すと約束している型と違うときは、? が From を使って変換しようとします。あなたの仕事は、その From を書いてあげることです。
use std::num::ParseIntError;
#[derive(Debug)]enum ConfigError { BadPort(ParseIntError),}
// ParseIntError を ConfigError に変える方法を `?` に教えるimpl From<ParseIntError> for ConfigError { fn from(err: ParseIntError) -> Self { ConfigError::BadPort(err) }}
fn parse_port(text: &str) -> Result<u16, ConfigError> { let port = text.parse::<u16>()?; Ok(port)}
fn main() { println!("{:?}", parse_port("8080")); println!("{:?}", parse_port("eighty"));}実行結果:
Ok(8080)Err(BadPort(ParseIntError { kind: InvalidDigit }))ほかにもよい方法があります。1か所だけ変換するなら .map_err(ConfigError::BadPort)?、thiserror クレートの #[from] を使う、小さなプログラムなら Result<T, Box<dyn std::error::Error>> を返す、などです。詳しくは独自のエラー型とanyhow と thiserrorを見てください。
直し方5:Rc はスレッド間で安全に送れない
Section titled “直し方5:Rc はスレッド間で安全に送れない”error[E0277]: `Rc<String>` cannot be sent between threads safely --> src/main.rs:6:32 |6 | let handle = thread::spawn(move || { | ------------- required by a bound introduced by this call ... = help: within `{closure@src/main.rs:6:32: 6:39}`, the trait `Send` is not implemented for `Rc<String>`「Rc<String> はスレッド間で安全に送れない」「クロージャの中の Rc<String> には Send が実装されていない」と言っています。
Rc は、所有者の数をふつうの数値で数えています。2つのスレッドが同時にその数を書き換えると、値が壊れてしまうかもしれません。そのため Rc は Send ではありません。代わりに Arc(「アトミック」版)を使いましょう。使い方は同じで、スレッドをまたいでも安全です。
use std::sync::Arc;use std::thread;
fn main() { let config = Arc::new(String::from("debug")); let for_thread = Arc::clone(&config); // スレッド用の2人目の所有者
let handle = thread::spawn(move || { println!("スレッドから見た値: {for_thread}"); }); handle.join().unwrap();
println!("mainもまだ持っている: {config}");}実行結果:
スレッドから見た値: debugmainもまだ持っている: debugRefCell も同じです。スレッドをまたぐときは、Rc<RefCell<T>> の代わりに Arc<Mutex<T>> を使います。詳しくは共有状態と並行処理を見てください。
よく出会う場面
Section titled “よく出会う場面”- ジェネリックな関数。
fn announce<T: Display>(item: T)に自分の構造体を渡すと、`Song` doesn't implement `std::fmt::Display`と「required by a bound inannounce」(announceのトレイト境界で必要とされている)が出ます。直し方:トレイト境界が求めているトレイトを実装します。 - テスト。
assert_eq!(a, b)にはPartialEqとDebugの両方が必要です(テストが失敗したときに値を表示するためです)。直し方:#[derive(Debug, PartialEq)]を付けます。 - 間違った型に
.collect()する。let nums: Vec<i32> = words.iter().collect();は「a value of typeVec<i32>cannot be built from an iterator over elements of type&String」(&Stringの要素を返すイテレータからはVec<i32>を作れない)になります。直し方:先に各要素を変換します。たとえば.map(|w| w.parse().unwrap())です。詳しくはイテレータを見てください。
関連するエラー
Section titled “関連するエラー”- E0599: no method found:キーが
HashでないHashMapに.insert()を呼ぶと、E0599(「trait bounds were not satisfied」)として出ることもあります。直し方は同じで、トレイトを derive します。 - E0308: mismatched types:ある型が必要な場所に別の型を渡した。
f64が入るところにi32を渡したときなどです。 - E0382: borrow of moved value:トレイトのことを考えて
CloneやCopyを付けた直後によく出てきます。 E0369: binary operation == cannot be applied:PartialEqがないことの==演算子版です。直し方も同じです。- コンパイルエラーの一覧
理解度チェック
Section titled “理解度チェック”理解度チェック
1. {:?} を使ったら「Order doesn't implement Debug」と出ました。いちばん手っ取り早い直し方はどれですか?
2. let total = 3_i32 + 2.5_f64; はなぜエラーになるのですか?
3. スレッドのコードで「Rc<String> cannot be sent between threads safely」と出ました。何を使えばよいですか?
スコア: 0 / 3
さらに詳しく
Section titled “さらに詳しく”- E0277 — Rust error codes index(英語):このエラーの公式の説明です。
- トレイト:共通の振る舞いを定義する — The Rust Programming Language 日本語版:トレイトとは何か、トレイト境界がどう働くかを説明しています。
- 付録C:導出可能なトレイト — The Rust Programming Language 日本語版:
Debug、PartialEq、Eq、Hash、PartialOrd、Ordを derive すると、それぞれ何ができるようになるかがわかります。 - Resultで回復可能なエラー — The Rust Programming Language 日本語版:
?のしくみと、Fromによる変換について説明しています。 Send— Rust 標準ライブラリ(英語):どの型がスレッド間を移動できるのか、Rcがなぜできないのかがわかります。