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

Ошибка 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 разделяет данные и поведение. Структура или перечисление описывает данные: чем значение является. Трейт описывает поведение: что тип умеет делать. 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 trait

rustc пишет: «ожидался трейт, а найдена структура 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 });
}

Вывод:

фигура, сторон: 3
TRIANGLE!
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 настоящее имя трейта.

Проверь себя

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