Ошибка Rust E0404: expected trait, found struct или derive macro (это не трейт)
E0404 значит: Rust нашёл имя, которое ты написал, но это не трейт, а в этом месте может стоять только трейт. Такие места — impl ___ for Type, ограничение вроде T: ___, а также dyn ___ или impl ___. Самый частый случай удивляет многих: impl Debug for Point без импорта. Debug есть в области видимости, но как макрос #[derive(Debug)], а не как трейт. Второй частый случай — структура, которую используют как трейт; так бывает, если ты привык к классам и наследованию. Оба исправляются быстро. (Эта страница входит в раздел Ошибки компилятора Rust с объяснениями.)
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0404]: expected trait, found derive macro `Debug` --> src/main.rs:6:6 |6 | impl Debug for Point { // ❌ `Debug` here is the derive macro, not the trait | ^^^^^ not a trait |help: consider importing this trait instead |1 + use std::fmt::Debug; |По-русски: «ожидался трейт, а найден derive-макрос Debug». В help rustc предлагает вместо него импортировать трейт.
Вот программа, которая её вызывает (комментарий // ❌ оставлен на английском, потому что rustc цитирует эту строку; он значит «Debug здесь — derive-макрос, а не трейт»):
struct Point { x: i32, y: i32,}
impl Debug for Point { // ❌ `Debug` here is the derive macro, not the trait fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Point at ({}, {})", self.x, self.y) }}
fn main() { let p = Point { x: 3, y: 4 }; println!("{:?}", p);}Что это значит
Заголовок раздела «Что это значит»^^^^^ указывает на Debug в строке 6 с пометкой «not a trait» («не трейт»). Заголовок говорит, что Rust нашёл вместо трейта: derive-макрос с именем Debug.
Дело в том, что вещей с именем Debug две. Макрос Debug, который ты пишешь как #[derive(Debug)], есть в прелюдии, поэтому его видит каждый файл. А трейт std::fmt::Debug, который нужен для impl Debug for ..., в прелюдию не входит. Поэтому Rust находит единственный видимый Debug, замечает, что это макрос, и останавливается. Строка help даёт импорт, который приносит трейт. С Hash всё так же: expected trait, found derive macro `Hash` , исправляется через use std::hash::Hash;.
Если в заголовке написано found struct `Animal` (или enum, или type alias), значит, это один из твоих собственных типов, использованный там, где должен быть трейт.
Это как попросить «человека с водительскими правами» и получить машину. Машина — настоящая вещь и тесно связана с делом, но просили того, кто умеет водить: набор умений. Трейт и есть такой набор.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Rust разделяет данные и поведение. Структура или перечисление описывает данные: чем значение является. Трейт описывает поведение: что тип умеет делать. impl Trait for Type связывает одно с другим: «этот тип умеет вот это». Ограничение вроде T: Trait значит «любой тип, который умеет вот это».
Структуру нельзя поставить в эти места, потому что она не список умений. А наследования в Rust нет, поэтому impl Animal for Dog не может значить «Dog — разновидность Animal», если Animal — структура. Если хочешь, чтобы несколько типов делили поведение, общая часть — это трейт. Подробнее — в уроках Трейты и Обобщения.
Кроме того, Rust держит макросы и трейты в разных пространствах имён, поэтому Debug может быть одновременно и макросом, и трейтом. Derive-макрос лежит в прелюдии, чтобы #[derive(Debug)] работал везде, а трейт нужно называть, только когда ты сам его реализуешь или ставишь в ограничение.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: Импортируй трейт (use std::fmt::Debug;)
Заголовок раздела «Исправление 1: Импортируй трейт (use std::fmt::Debug;)»Выбирай этот вариант, когда в сообщении написано found derive macro и тебе нужен свой формат или трейт нужен в ограничении вроде T: Debug. Добавь строку из help:
use std::fmt::Debug;
struct Point { x: i32, y: i32,}
impl Debug for Point { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "Точка ({}, {})", self.x, self.y) }}
fn print_all<T: Debug>(items: &[T]) { for item in items { println!("{:?}", item); }}
fn main() { let p = Point { x: 3, y: 4 }; println!("{:?}", p); print_all(&[Point { x: 0, y: 0 }, Point { x: -1, y: 7 }]);}Вывод:
Точка (3, 4)Точка (0, 0)Точка (-1, 7)Частая альтернатива — use std::fmt;, а затем impl fmt::Debug for Point с fmt::Formatter и fmt::Result. Оба варианта нормальные; просто выбери один стиль на файл.
Исправление 2: Используй #[derive(Debug)] вместо ручной реализации
Заголовок раздела «Исправление 2: Используй #[derive(Debug)] вместо ручной реализации»Выбирай этот вариант, когда особый формат не нужен, — а для Debug так бывает чаще всего. Derive-макрос пишет весь impl Debug for Point за тебя, и никакой импорт не нужен:
#[derive(Debug)] // ✅ derive-макрос сам пишет `impl Debug for Point`struct Point { x: i32, y: i32,}
fn main() { let p = Point { x: 3, y: 4 }; println!("{:?}", p); println!("{:#?}", p); println!("расстояние от (0, 0) по клеткам: {}", p.x.abs() + p.y.abs());}Вывод:
Point { x: 3, y: 4 }Point { x: 3, y: 4,}расстояние от (0, 0) по клеткам: 7Используй что-то одно, а не оба сразу: #[derive(Debug)] вместе с собственным impl Debug дают ошибку «conflicting implementations» («конфликтующие реализации», E0119, на английском). {:#?} — красивая многострочная форма. Подробнее — в уроке Распространённые трейты.
Исправление 3: Определи трейт для общего поведения
Заголовок раздела «Исправление 3: Определи трейт для общего поведения»Выбирай этот вариант, когда в строке impl X for Y сообщение пишет found struct. Скорее всего, ты хотел, чтобы Dog был «разновидностью Animal», как подкласс:
error[E0404]: expected trait, found struct `Animal` --> src/main.rs:7:6 |7 | impl Animal for Dog { // ❌ `Animal` is a struct, not a trait | ^^^^^^ not a traitrustc пишет: «ожидался трейт, а найдена структура Animal». Программа, которая к этому приводит (комментарий значит «Animal — структура, а не трейт»):
struct Animal { name: String,}
struct Dog;
impl Animal for Dog { // ❌ `Animal` is a struct, not a trait fn name(&self) -> String { String::from("Rex") }}
fn main() { println!("{}", Dog.name());}В Rust общая часть — это трейт: список методов, которые должен предоставить каждый тип, плюс необязательные методы по умолчанию. Данные остаются в каждой структуре:
trait Animal { // ✅ трейт: что умеет любое животное fn name(&self) -> String;
fn greet(&self) -> String { // метод по умолчанию format!("Привет, я {}!", self.name()) }}
struct Dog;
struct Cat { name: String, // данные живут в структуре}
impl Animal for Dog { fn name(&self) -> String { String::from("Рекс") }}
impl Animal for Cat { fn name(&self) -> String { self.name.clone() }}
fn main() { println!("{}", Dog.greet()); let tom = Cat { name: String::from("Том") }; println!("{}", tom.greet());}Вывод:
Привет, я Рекс!Привет, я Том!Если ты хотел добавить методы только одной структуре, трейт вообще не нужен: пиши impl Dog { fn name(&self) -> String { ... } }, без for. Подробнее — в уроке Трейты.
Исправление 4: Принимай саму структуру или ставь ограничение на трейт
Заголовок раздела «Исправление 4: Принимай саму структуру или ставь ограничение на трейт»Выбирай этот вариант, когда ошибка указывает на ограничение вроде T: Shape или impl Shape, а Shape — твоя структура:
error[E0404]: expected trait, found struct `Shape` --> src/main.rs:5:16 |5 | fn describe<T: Shape>(item: &T) { // ❌ `Shape` is a struct, not a trait | ^^^^^ not a traitПрограмма, которая к этому приводит (комментарий значит «Shape — структура, а не трейт»):
struct Shape { sides: u32,}
fn describe<T: Shape>(item: &T) { // ❌ `Shape` is a struct, not a trait println!("a shape");}
fn main() { describe(&Shape { sides: 3 });}Ограничение значит «любой тип, который реализует этот трейт». Если функция всегда получает только Shape, убери обобщение и принимай &Shape. Если она должна работать со многими типами, вынеси общее поведение в трейт и ограничивай им:
struct Shape { sides: u32,}
fn describe(item: &Shape) { // ✅ принимаем саму структуру, обобщение не нужно println!("фигура, сторон: {}", item.sides);}
trait Named { // ✅ или трейт для «любого типа, у которого есть имя» fn name(&self) -> String;}
impl Named for Shape { fn name(&self) -> String { match self.sides { 3 => String::from("triangle"), 4 => String::from("square"), n => format!("{}-gon", n), } }}
fn shout<T: Named>(item: &T) { // ✅ ограничению трейта нужен трейт println!("{}!", item.name().to_uppercase());}
fn main() { let t = Shape { sides: 3 }; describe(&t); shout(&t); shout(&Shape { sides: 6 });}Вывод:
фигура, сторон: 3TRIANGLE!6-GON!Названия фигур оставлены на английском, потому что программа переводит их в верхний регистр через to_uppercase. Ограничения трейтов объясняются в уроках Обобщения и Трейты.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»impl Vec<i32> как тип параметра. fn total(x: impl Vec<i32>) даёт expected trait, found struct `Vec` . impl в этом месте требует трейт. Исправление: принимай &[i32] или Vec<i32> либо impl IntoIterator<Item = i32>, если хочешь принимать любую коллекцию.
Box<dyn String>. dyn тоже требует трейт, поэтому это даёт expected trait, found struct `String` , и rustc предлагает ToString. Исправление: Box<dyn ToString>, если ты имел в виду «всё, что можно превратить в строку», или просто String. Подробнее — в уроке Трейт-объекты.
Псевдоним типа для трейта. type Printable = dyn std::fmt::Display;, а потом impl Printable for A дают expected trait, found type alias `Printable` с пометкой «type aliases cannot be used as traits» («псевдонимы типов нельзя использовать как трейты»). Исправление: напиши в impl настоящее имя трейта.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0405: cannot find trait in this scope: имени вообще нет, например
Displayбез импорта. E0404 значит, что имя есть, но это не та сущность. - E0277: the trait bound is not satisfied: трейт правильный, но тип его не реализует.
- E0425: cannot find value in this scope: такая же ошибка «не найдено» для значений, функций и типов.
- E0107: wrong number of generic arguments: трейт или тип правильный, но аргументов в
<...>слишком много или слишком мало. - E0119: conflicting implementations (на английском):
#[derive(Debug)]и собственныйimpl Debugу одного типа. - Все ошибки компилятора с объяснениями
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. impl Debug for Point даёт «expected trait, found derive macro Debug». Как проще всего исправить, если нужен свой формат?
2. У тебя есть struct Animal, и ты пишешь impl Animal for Dog. Чего Rust хочет вместо этого?
3. Что из этого может стоять после T: в fn show<T: ___>(x: T)?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0404 — Rust error codes index (на английском): официальное объяснение с примером структуры, использованной как трейт.
- Трейты: определение общего поведения — книга «Язык программирования Rust» (перевод на русский): как определить трейт, реализовать его и использовать в ограничении.
std::fmt::Debug— документация стандартной библиотеки (на английском):derive(Debug)против ручной реализации.- Типажи — Rust на примерах (перевод на русский): трейт, общий для нескольких типов, с методами по умолчанию.
std::prelude— документация стандартной библиотеки (на английском): что доступно в каждом файле — включая derive-макросDebug, но не трейт.