Ошибка 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 так делает
Заголовок раздела «Почему Rust так делает»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.5f64::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 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Заметка объясняет главное: оператор ? неявно преобразует значение ошибки с помощью трейта 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 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» («значение типаVec<i32>нельзя собрать из итератора по элементам типа&String»). Исправление: сначала преобразуй каждый элемент, например.map(|w| w.parse().unwrap()). См. Итераторы.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0599: no method found: вызов
.insert()уHashMap, ключ которого неHash, иногда выглядит как E0599 («trait bounds were not satisfied»). Исправление то же: выведи трейты. - E0308: mismatched types: ты передал один тип там, где ожидался другой, например
i32вместоf64. - E0382: borrow of moved value: часто появляется сразу после того, как ты добавил
CloneилиCopy, думая о трейтах. E0369: binary operation == cannot be applied: версия недостающегоPartialEqдля оператора==. Исправление то же.- Все ошибки компилятора с объяснениями
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
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
Что почитать
Заголовок раздела «Что почитать»- E0277 — Rust error codes index (на английском): официальное объяснение этой ошибки.
- Трейты: определение общего поведения — книга «Язык программирования Rust» (перевод на русский): что такое трейты и как работают ограничения трейтов.
- Приложение C: Выводимые трейты — книга «Язык программирования Rust» (перевод на русский): что дают
Debug,PartialEq,Eq,Hash,PartialOrdиOrd, если их вывести через derive. - Устранимые ошибки с Result — книга «Язык программирования Rust» (перевод на русский): как работает
?, включая преобразование черезFrom. Send— стандартная библиотека Rust (на английском): какие типы можно передавать между потоками и почемуRcнельзя.