コンテンツにスキップ

Rustのエラー E0277: the trait bound is not satisfied(トレイト境界を満たしていない)

E0277 は、ある能力が必要な場所に、その能力を持っていない型を使ったという意味のエラーです。表示する、比較する、足し算する、変換する、別のスレッドに送る。どれもトレイト(trait)が必要で、あなたの型にはそのトレイトが足りていません。Rust でとてもよく出るエラーの1つですが、メッセージがたいてい直し方をそのまま教えてくれますし、ほとんどの場合は1行で直せます。(このページはRustのコンパイルエラー解説の一部です。)

多くの人が最初に出会うのは、自分で作った構造体を {} で表示しようとしたときです。

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);
}

トレイトとは、「この型はこれができます」という約束のことです。Display は「自分をきれいに表示する方法を知っています」という約束です。PartialEq は「== で比較できます」、Send は「別のスレッドに移しても安全です」という約束です。

println!、.sort()、+、thread::spawn といったコードは、こうした約束のどれかを求めます。コンパイラは、あなたが渡した値(8行目の p)を指して、「この型はその約束をしていないので、ここでは使えません」と言っているのです。

コンセントを思い浮かべてみてください。コンセントは決まった形のプラグしか受け付けません。電化製品そのものは完璧に動くとしても、プラグの形が合わなければ差し込めません。E0277 は、コンパイラが「プラグの形が違います」と言っているようなものです。直すには、型に正しいプラグを付けてあげます。たいていは #[derive(...)] で、ときどき短い impl ブロックを書きます。

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を含む: true
Lowのタスク数: 1
▶ 実際に試してみよう

derive した 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.5
▶ 実際に試してみよう

f64::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 this
9 | 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}");
}

実行結果:

スレッドから見た値: debug
mainもまだ持っている: debug
▶ 実際に試してみよう

RefCell も同じです。スレッドをまたぐときは、Rc<RefCell<T>> の代わりに Arc<Mutex<T>> を使います。詳しくは共有状態と並行処理を見てください。

  • ジェネリックな関数。 fn announce<T: Display>(item: T) に自分の構造体を渡すと、`Song` doesn't implement `std::fmt::Display` と「required by a bound in announce」(announce のトレイト境界で必要とされている)が出ます。直し方:トレイト境界が求めているトレイトを実装します。
  • テスト。 assert_eq!(a, b) には PartialEq と Debug の両方が必要です(テストが失敗したときに値を表示するためです)。直し方:#[derive(Debug, PartialEq)] を付けます。
  • 間違った型に .collect() する。 let nums: Vec<i32> = words.iter().collect(); は「a value of type Vec<i32> cannot be built from an iterator over elements of type &String」(&String の要素を返すイテレータからは Vec<i32> を作れない)になります。直し方:先に各要素を変換します。たとえば .map(|w| w.parse().unwrap()) です。詳しくはイテレータを見てください。

理解度チェック

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