Rust 에러 E0404: expected trait, found struct / derive macro (트레이트가 아님)
E0404는 Rust가 내가 쓴 이름을 찾긴 했는데, 그게 트레이트가 아니고, 그 자리에는 트레이트만 올 수 있다는 뜻이에요. 그런 자리는 impl ___ for Type, T: ___ 같은 바운드, 그리고 dyn ___나 impl ___예요. 가장 흔한 경우는 많은 사람을 놀라게 해요. 가져오지(import) 않고 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`는 트레이트가 아니라 derive 매크로예요 | ^^^^^ not a trait |help: consider importing this trait instead |1 + use std::fmt::Debug; |“expected trait, found derive macro Debug“는 “트레이트가 와야 하는데 derive 매크로 Debug가 왔다”는 뜻이에요. help:는 “대신 이 트레이트를 가져오는 걸 고려해 보라”며 use 줄을 제안해요.
이 에러를 내는 프로그램은 다음과 같아요.
struct Point { x: i32, y: i32,}
impl Debug for Point { // ❌ 여기서 `Debug`는 트레이트가 아니라 derive 매크로예요 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "위치 ({}, {})", self.x, self.y) }}
fn main() { let p = Point { x: 3, y: 4 }; println!("{:?}", p);}무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”^^^^^는 6번째 줄의 Debug를 가리키고, 라벨은 “not a trait”(트레이트가 아니다)예요. 첫 줄은 Rust가 대신 무엇을 찾았는지 알려 줘요. Debug라는 derive 매크로예요.
Debug라는 이름을 가진 것이 두 개 있기 때문이에요. #[derive(Debug)]로 쓰는 매크로 Debug는 프렐루드(prelude)에 있어서 모든 파일에서 보여요. impl Debug for ...에 필요한 트레이트 std::fmt::Debug는 프렐루드에 없어요. 그래서 Rust는 자기 눈에 보이는 유일한 Debug를 찾아서, 그게 매크로라는 걸 알고 멈춰요. help 줄은 트레이트를 가져오는 use 줄을 알려 줘요. 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에는 상속이 없어서, Animal이 구조체일 때 impl Animal for Dog가 “Dog는 Animal의 한 종류다”라는 뜻이 될 수 없어요. 여러 타입이 동작을 공유하게 하고 싶다면, 공유하는 부분은 트레이트예요. 트레이트와 제네릭을 참고하세요.
또 Rust는 매크로와 트레이트를 서로 다른 이름 공간(namespace)에 두기 때문에, 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;를 쓰고 fmt::Formatter, fmt::Result와 함께 impl fmt::Debug for Point라고 쓰는 거예요. 둘 다 괜찮아요. 파일마다 한 가지 스타일을 고르세요.
방법 2: impl을 직접 쓰는 대신 #[derive(Debug)] 쓰기
섹션 제목: “방법 2: impl을 직접 쓰는 대신 #[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`은 트레이트가 아니라 구조체예요 | ^^^^^^ not a trait문제가 되는 프로그램은 이래요.
struct Animal { name: String,}
struct Dog;
impl Animal for Dog { // ❌ `Animal`은 트레이트가 아니라 구조체예요 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("Rex") }}
impl Animal for Cat { fn name(&self) -> String { self.name.clone() }}
fn main() { println!("{}", Dog.greet()); let tom = Cat { name: String::from("Tom") }; println!("{}", tom.greet());}실행 결과:
Rex: 안녕하세요!Tom: 안녕하세요!구조체 하나에 메서드를 추가하고 싶었을 뿐이라면 트레이트는 전혀 필요 없어요. for 없이 impl Dog { fn name(&self) -> String { ... } }라고 쓰세요. 트레이트 정의하기를 참고하세요.
방법 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`는 트레이트가 아니라 구조체예요 | ^^^^^ not a trait문제가 되는 프로그램은 이래요.
struct Shape { sides: u32,}
fn describe<T: Shape>(item: &T) { // ❌ `Shape`는 트레이트가 아니라 구조체예요 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에는 트레이트의 진짜 이름을 쓰세요.
관련 에러
섹션 제목: “관련 에러”- 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. fn show<T: ___>(x: T)의 T: 뒤에 올 수 있는 것은 무엇일까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0404 — Rust error codes index (영어): 구조체를 트레이트로 쓴 예제가 있는 공식 설명이에요.
- 트레이트로 공통된 동작을 정의하기 — The Rust Programming Language 한국어판: 트레이트를 정의하고, 구현하고, 바운드로 쓰는 법이에요.
std::fmt::Debug— standard library docs (영어):Debug를 derive하는 것과 직접 구현하는 것의 차이예요.- Traits — Rust by Example (영어): 기본 메서드가 있는 트레이트를 여러 타입이 공유하는 예제예요.
std::prelude— standard library docs (영어): 모든 파일에서 스코프에 있는 것들의 목록이에요.Debugderive 매크로는 있지만 트레이트는 없어요.