Перейти к содержимому

Ошибка Rust E0277: the trait bound is not satisfied (трейт не реализован)

Ошибка E0277 значит: ты использовал тип там, где нужна способность, которой у этого типа нет. Вывести его на экран, сравнить, сложить, преобразовать, отправить в другой поток — для каждого из этих действий нужен трейт (trait), а у твоего типа его нет. Это одна из самых частых ошибок в Rust, сообщение обычно подсказывает точное исправление, и чаще всего оно занимает одну строку. (Эта страница — часть раздела Ошибки компилятора 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, поэтому его нельзя вывести обычным форматированием. В заметке он предлагает попробовать {:?} (или {:#?} для красивого вывода).

Программа, которая вызывает ошибку:

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 требует одно из этих обещаний. Компилятор указывает на значение, которое ты передал (p в строке 8), и говорит: этот тип такого обещания никогда не давал, поэтому использовать его здесь я не могу.

Представь розетку. Она принимает вилки только одной формы. Твой прибор может работать идеально, но если вилка не подходит, в розетку она не войдёт. E0277 — это компилятор, который говорит: «не та вилка». Исправление — дать типу правильную вилку: обычно через #[derive(...)], иногда через короткий блок impl.

Rust никогда не угадывает. Для твоего собственного типа Player нет единственного «правильного» способа его вывести, сравнить или отсортировать. Равны ли два игрока, когда совпадают имена, или только когда совпадают все поля? Rust заставляет тебя сказать это явно, а не тихо делает то, чего ты не хотел.

Проверка происходит во время компиляции. Во многих языках вывод объекта, который ты не научил выводиться, даёт что-то бесполезное вроде <Player object at 0x7f...>, а передача потоконебезопасного значения в поток — редкое падение в продакшене. В Rust и то и другое — ошибки компиляции, которые исправляются один раз.

Подробнее о том, как работают трейты, — в уроке Трейты, а о повседневных трейтах (Debug, Display, PartialEq, Ord, Hash) — в уроке Популярные трейты.

У E0277 много лиц. Найди ниже сообщение, похожее на твоё.

Исправление 1: вывод структуры через {} или {:?}

Заголовок раздела «Исправление 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`

Компилятор прямо предлагает: добавь #[derive(Debug)] к Player или реализуй Debug вручную.

{} требует Display. Rust не умеет выводить его автоматически, потому что только ты знаешь, как должна выглядеть «красивая» версия. Напиши маленький impl с write!. Это для текста, который увидят пользователи.

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

Заголовок раздела «Исправление 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 нельзя сравнить с 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
▶ Попробуй сам

Выведенный Ord сортирует варианты перечисления в том порядке, в каком ты их написал, а структуры — поле за полем сверху вниз. Честное предупреждение: тип с полем f64 не может вывести Eq, Hash или Ord, потому что у чисел с плавающей точкой есть NaN, который не равен сам себе. Как сортировать числа с плавающей точкой, рассказано в уроке Сортировка и поиск.

Исправление 3: смешивание числовых типов (cannot add f64 to i32)

Заголовок раздела «Исправление 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`

«Нельзя прибавить f64 к i32»: для 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) дробная часть отбрасывается, так что сначала подумай об округлении. Подробности — в уроках Приведение типов и Преобразования (From, Into).

Исправление 4: ? не может преобразовать ошибку

Заголовок раздела «Исправление 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

Заметка объясняет главное: оператор ? неявно преобразует значение ошибки с помощью трейта 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("восемьдесят"));
}

Вывод:

Ok(8080)
Err(BadPort(ParseIntError { kind: InvalidDigit }))
▶ Попробуй сам

Другие хорошие варианты: преобразовать только один вызов через .map_err(ConfigError::BadPort)?, использовать #[from] из крейта thiserror или в маленьких программах возвращать Result<T, Box<dyn std::error::Error>>. См. Свои типы ошибок и anyhow и thiserror.

Исправление 5: Rc нельзя безопасно передать между потоками

Заголовок раздела «Исправление 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> нельзя безопасно передать между потоками»: для него не реализован Send. Rc считает своих владельцев обычным числом, которое два потока могли бы изменить одновременно и испортить. Поэтому 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); // второй владелец — для потока
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» («значение типа Vec<i32> нельзя собрать из итератора по элементам типа &String»). Исправление: сначала преобразуй каждый элемент, например .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