콘텐츠로 이동

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예요.

휴대폰 연락처에 “엄마”라는 항목이 두 개 있는 것과 같아요. “엄마한테 전화해 줘”라고 하면 어느 번호로 걸어야 할까요?

메서드를 여러 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 블록은 크레이트의 어느 모듈에든 있을 수 있어요.

두 정의가 서로 다른 일을 할 때 이 방법을 고르세요. 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) 후: 10
reset() 후: 0
▶ 직접 실행해 보기

Rust에서 흔히 쓰는 이름 짓기 패턴이 도움이 돼요. 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)로 고치세요.

이해도 점검

1. 한 타입에 impl 블록이 두 개 있어요. 두 블록 모두 reset이라는 메서드를 가질 수 있을까요?

2. area()와 area(scale: f64)를 둘 다 원해요. Rust에서는 어떻게 할까요?

3. impl<T> Wrapper<T>와 impl Wrapper<i32>가 둘 다 describe를 정의하면 왜 충돌할까요?

점수: 0 / 3