Rust 에러 E0592: duplicate definitions with name X (메서드 중복 정의)
E0592는 한 타입에 이름이 같은 메서드, 연관 함수, 상수가 impl 블록 안에 두 개 있다는 뜻이에요. 한 타입에 impl 블록은 얼마든지 만들 수 있지만, 모든 블록이 이름 목록 하나를 함께 써요. 그래서 각 이름은 한 번만 쓸 수 있어요. 보통 복사해 붙여넣기, 병합(merge), 다른 파일에 있는 두 번째 impl 블록 때문에 생겨요. 하나만 남기거나 다른 하나에 새 이름을 붙이면 돼요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.
에러 메시지
섹션 제목: “에러 메시지”error[E0592]: duplicate definitions with name `reset` --> src/main.rs:10:5 |10 | fn reset(&mut self) { | ^^^^^^^^^^^^^^^^^^^ duplicate definitions for `reset`...16 | fn reset(&mut self) { // ❌ `reset`은 위에서 이미 정의했어요 | ------------------- other definition for `reset`“duplicate definitions with name reset“은 “reset이라는 이름의 정의가 중복됐다”는 뜻이에요. 라벨은 “reset의 중복 정의”와 “reset의 다른 정의”를 가리켜요.
이 에러를 내는 프로그램은 다음과 같아요.
struct Counter { count: u32,}
impl Counter { fn new() -> Self { Counter { count: 0 } }
fn reset(&mut self) { self.count = 0; }}
impl Counter { fn reset(&mut self) { // ❌ `reset`은 위에서 이미 정의했어요 self.count = 0; }}
fn main() { let mut c = Counter::new(); c.reset(); println!("{}", c.count);}rustc는 c.reset() 호출에서 E0034, “multiple applicable items in scope”(스코프에 적용할 수 있는 항목이 여러 개 있다)도 알려 줘요. 두 reset 중 어느 것을 말하는지 알 수 없기 때문이에요. 이건 뒤따라 나오는 에러라서, E0592를 고치면 E0034도 사라져요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”첫 줄에는 두 번 정의된 이름(reset)이 나와요. ^^^는 한 정의를, --- 줄은 “다른 정의”를 표시해요. rustc가 두 곳의 줄 번호를 모두 보여 주니까 바로 찾아갈 수 있어요. 둘이 멀리 떨어져 있으면 사이에 ...가 나오는데, 그냥 “몇 줄 건너뜀”이라는 뜻이에요.
두 reset이 서로 다른 impl Counter 블록에 있든, 크레이트의 다른 파일에 있든 상관없어요. Rust에게는 모두 같은 타입의 메서드라서, Counter::reset은 정확히 함수 하나를 뜻해야 해요. 연관 상수에도 같은 규칙이 적용돼요. 한 타입에 const MAX: u32가 두 개 있어도 E0592예요.
휴대폰 연락처에 “엄마”라는 항목이 두 개 있는 것과 같아요. “엄마한테 전화해 줘”라고 하면 어느 번호로 걸어야 할까요?
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”메서드를 여러 impl 블록에 나눠 쓰는 건 허용되고, 쓸모도 많아요. 생성자용 블록, getter용 블록, #[cfg(test)] 뒤에 둔 블록처럼요. 메서드로 동작 추가하기를 참고하세요. 하지만 c.reset()이나 Counter::reset(&mut c)를 호출하면 Rust는 함수를 하나 찾아야 해요. 두 개가 있다면 어느 쪽이 실행될지가 호출 코드를 읽어서는 아무도 알 수 없는 세부 사항에 달려 버려요.
그래서 Rust는 호출할 때뿐 아니라 정의할 때 거부해요. 이건 실제 버그를 잡아 줘요. 복사해 붙여넣은 메서드라면 한쪽 사본의 문제만 고치고 다른 사본은 계속 실행될 수 있거든요. Rust는 고칠 버전이 언제나 하나뿐이도록 보장해요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 중복을 지우고 정의 하나만 남기기
섹션 제목: “방법 1: 중복을 지우고 정의 하나만 남기기”대부분은 이 방법을 고르세요. 두 사본이 같은 일을 한다면(복사해 붙여넣기, 엉킨 병합, 옮겨 놓고 지우지 않은 메서드) 하나를 지우세요. 지우기 전에 두 본문을 비교하세요. 한쪽에만 수정 사항이 있다면 그쪽을 남기세요. impl 블록을 하나로 합쳐 두면 나중에 중복을 찾기도 쉬워져요.
struct Counter { count: u32,}
impl Counter { fn new() -> Self { Counter { count: 0 } }
fn increment(&mut self) { self.count += 1; }
fn reset(&mut self) { // ✅ `Counter`에는 `reset`이 하나뿐이에요 self.count = 0; }}
fn main() { let mut c = Counter::new(); c.increment(); c.increment(); println!("초기화 전: {}", c.count); c.reset(); println!("초기화 후: {}", c.count);}실행 결과:
초기화 전: 2초기화 후: 0두 번째 정의를 못 찾겠다면 프로젝트 전체를 검색하세요. grep -rn "fn reset" src/. impl Counter 블록은 크레이트의 어느 모듈에든 있을 수 있어요.
방법 2: 하나의 이름 바꾸기
섹션 제목: “방법 2: 하나의 이름 바꾸기”두 정의가 서로 다른 일을 할 때 이 방법을 고르세요. Rust에는 오버로딩(overloading)이 없어요. reset()과 reset(start: u32)처럼 이름이 같고 매개변수가 다른 메서드 두 개를 둘 수 없어요. 그것도 E0592예요. 각각 하는 일을 말해 주는 이름을 붙이세요.
struct Counter { count: u32,}
impl Counter { fn new() -> Self { Counter { count: 0 } }
fn reset(&mut self) { self.count = 0; }}
impl Counter { fn reset_to(&mut self, start: u32) { // ✅ 다른 동작에는 다른 이름 self.count = start; }}
fn main() { let mut c = Counter::new(); c.reset_to(10); println!("reset_to(10) 후: {}", c.count); c.reset(); println!("reset() 후: {}", c.count);}실행 결과:
reset_to(10) 후: 10reset() 후: 0Rust에서 흔히 쓰는 이름 짓기 패턴이 도움이 돼요. new와 with_capacity, from_str과 from_bytes, get과 get_mut처럼요. 정말로 여러 인자 타입에 이름 하나를 쓰고 싶다면 fn reset_to(&mut self, start: impl Into<u32>)처럼 제네릭 매개변수나 트레이트를 쓰세요. 제네릭을 참고하세요.
방법 3: 제네릭 impl 블록이 겹치지 않게 하기
섹션 제목: “방법 3: 제네릭 impl 블록이 겹치지 않게 하기”두 정의 중 하나가 impl<T> Wrapper<T> 같은 제네릭 블록에 있을 때 이 방법을 고르세요. 그 블록은 Wrapper<i32>도 포함하니까, 같은 메서드 이름을 가진 두 번째 impl Wrapper<i32>는 중복이에요.
error[E0592]: duplicate definitions with name `describe` --> src/main.rs:4:5 | 4 | fn describe(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definitions for `describe`...10 | fn describe(&self) -> String { | ---------------------------- other definition for `describe`이 에러를 내는 프로그램이에요.
struct Wrapper<T>(T);
impl<T> Wrapper<T> { fn describe(&self) -> String { String::from("something") }}
impl Wrapper<i32> { fn describe(&self) -> String { format!("the number {}", self.0) }}
fn main() { println!("{}", Wrapper(5).describe());}Rust에서는 구체적인 impl이 제네릭 impl을 덮어쓸 수 없어요(특수화(specialization)라는 이 기능은 아직 stable이 아니에요). 좋은 해결책이 두 가지 있어요. 겹칠 수 없도록 구체적인 타입에 대해서만 impl 블록을 쓰거나, 동작을 트레이트에 넣고 타입마다 한 번씩 구현하세요.
struct Wrapper<T>(T);
// 방법 A: 구체적인 타입에 대한 impl 블록은 절대 겹치지 않아요impl Wrapper<i32> { fn describe(&self) -> String { format!("숫자 {}", self.0) }}
impl Wrapper<&str> { fn describe(&self) -> String { format!("텍스트 \"{}\"", self.0) }}
// 방법 B: 트레이트를 타입마다 한 번씩 구현해요trait Label { fn label(&self) -> String;}
impl Label for i32 { fn label(&self) -> String { format!("정수 {}", self) }}
impl Label for bool { fn label(&self) -> String { format!("플래그 {}", self) }}
impl<T: Label> Wrapper<T> { fn label(&self) -> String { self.0.label() // 제네릭 메서드는 하나, 타입마다 다른 부분은 트레이트에 있어요 }}
fn main() { println!("{}", Wrapper(5).describe()); println!("{}", Wrapper("hi").describe()); println!("{}", Wrapper(7).label()); println!("{}", Wrapper(true).label());}실행 결과:
숫자 5텍스트 "hi"정수 7플래그 true길게 보면 보통 트레이트 쪽이 더 좋은 설계예요. 새 타입을 지원하려면 impl Label for ...를 하나 더 쓰기만 하면 되거든요. 트레이트를 참고하세요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”두 번째 impl 블록에 넣은 테스트 도우미. 테스트용 값을 만들려고 #[cfg(test)] mod tests 안에 impl Counter { fn new() -> Self { ... } }를 추가했는데, Counter::new가 이미 있는 경우예요. 테스트 블록도 결국 impl Counter예요. 해결: fn for_test() 같은 이름을 붙이거나 진짜 new를 쓰세요. 테스트를 참고하세요.
병합이나 리팩터링 뒤. 두 브랜치가 둘 다 fn len(&self)를 추가했거나, 메서드를 새 파일로 옮겼는데 예전 사본이 남아 있는 경우예요. rustc가 두 줄 번호를 모두 보여 주니, 더 나은 쪽을 남기세요.
이름이 같은 트레이트 메서드는 괜찮아요. impl Counter { fn reset(&mut self) }와 impl Resettable for Counter { fn reset(&mut self) }가 함께 있는 건 E0592가 아니에요. 하나는 타입 자신의 메서드이고, 다른 하나는 트레이트에 속한 메서드예요. c.reset()은 타입 자신의 메서드를 호출해요. 메서드 이름이 같은 트레이트 두 개도 컴파일되지만, c.reset()으로 호출하면 E0034가 나요. Resettable::reset(&mut c)로 고치세요.
관련 에러
섹션 제목: “관련 에러”- E0034: multiple applicable items in scope: 호출하는 곳에서 뒤따라 나오는 에러이자, 두 트레이트가 같은 이름의 메서드를 가질 때의 에러예요.
- E0428: the name is defined multiple times: 모듈 안의 항목에서 하는 같은 실수예요.
struct Counter두 개나 자유 함수fn main두 개처럼요. - E0599: no method named x found: 반대 문제예요. 메서드가 아예 존재하지 않아요.
- E0255: name already used by an import와 E0252: name imported twice:
use때문에 생기는 이름 중복이에요. - 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 한 타입에 impl 블록이 두 개 있어요. 두 블록 모두 reset이라는 메서드를 가질 수 있을까요?
2. area()와 area(scale: f64)를 둘 다 원해요. Rust에서는 어떻게 할까요?
3. impl<T> Wrapper<T>와 impl Wrapper<i32>가 둘 다 describe를 정의하면 왜 충돌할까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0592 — Rust error codes index (영어): 이 에러의 공식 설명이에요.
- 메서드 문법 — The Rust Programming Language 한국어판: 메서드, 연관 함수, 여러 개의
impl블록을 다뤄요. - 제네릭 데이터 타입 — The Rust Programming Language 한국어판:
impl<T> Point<T>와impl Point<f32>를 나란히 쓰는 예제와 둘의 관계예요. - Implementations — The Rust Reference (영어): 고유
impl블록(inherent impl)과 그 연관 항목의 정확한 규칙이에요. - E0034 — Rust error codes index (영어): 뒤따라 나오는 “multiple applicable items” 에러의 공식 페이지예요.