콘텐츠로 이동

Rust 에러 E0034: multiple applicable items in scope (같은 이름의 메서드 충돌)

E0034는 내 타입에 이름이 같은 메서드가 두 개 있고(두 트레이트에서 하나씩), 그걸 book.describe()처럼 짧게 호출했다는 뜻이에요. Rust 눈에는 둘 다 보이고, 그중 하나를 대신 골라 주지 않아요. 타입이 잘못된 건 전혀 없어요. 어느 트레이트를 말하는지만 알려 주면 돼요. 보통 한 줄이면 되고, 그 줄도 rustc가 써 줘요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.

error[E0034]: multiple applicable items in scope
--> src/main.rs:28:25
|
28 | println!("{}", book.describe()); // ❌ Summary의 것일까요, Label의 것일까요?
| ^^^^^^^^ multiple `describe` found
|
note: candidate #1 is defined in an impl of the trait `Label` for the type `Book`
--> src/main.rs:21:5
|
21 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: candidate #2 is defined in an impl of the trait `Summary` for the type `Book`
--> src/main.rs:15:5
|
15 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the method for candidate #1
|
28 - println!("{}", book.describe()); // ❌ Summary의 것일까요, Label의 것일까요?
28 + println!("{}", Label::describe(&book)); // ❌ Summary의 것일까요, Label의 것일까요?
|
help: disambiguate the method for candidate #2
|
28 - println!("{}", book.describe()); // ❌ Summary의 것일까요, Label의 것일까요?
28 + println!("{}", Summary::describe(&book)); // ❌ Summary의 것일까요, Label의 것일까요?
|

“multiple applicable items in scope”는 “스코프 안에 적용할 수 있는 항목이 여러 개 있다”는 뜻이에요. 아래에서 한 줄씩 풀어 볼게요.

이 에러를 내는 프로그램은 다음과 같아요.

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn describe(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String {
format!("{} ({}쪽)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("BOOK: {}", self.title.to_uppercase())
}
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", book.describe()); // ❌ Summary의 것일까요, Label의 것일까요?
}

메시지를 위에서 아래로 읽어 보세요.

  1. describe 아래의 ^^^^^^^^: “multiple describe found”(describe가 여러 개 있다). Rust가 Book에서 describe라는 메서드를 찾았더니 하나가 넘었어요.
  2. 두 note: 줄은 후보(candidate)를 나열해요. 하나는 impl Label for Book(21번째 줄)에서, 다른 하나는 impl Summary for Book(15번째 줄)에서 와요. 둘 다 진짜이고 둘 다 동작하지만, 돌려주는 글은 달라요.
  3. 두 help: 줄은 “후보 #1(또는 #2)로 메서드를 명확히 하라”며 각각 고친 코드를 보여 줘요. Summary::describe(&book)은 “Summary에서 온 describe를, &book을 self로 해서 호출해”라는 뜻이에요.

한 사무실에 “민수”라는 동료가 두 명 있다고 생각해 보세요. “민수 씨!“라고 부르면 곤란하죠. “회계팀 민수 씨”라고 불러야 해요. Summary::describe가 바로 “회계팀 민수 씨”예요.

book.describe() 같은 메서드 호출은 지름길이에요. Rust는 book의 타입을 보고, 그 타입 자신의 메서드와, 스코프 안에 있으면서 Book에 구현된 모든 트레이트의 메서드를 찾아봐요. 보통은 딱 하나가 나와요. 여기서는 두 개가 나왔고, 하는 일도 달라요. 하나는 요약을, 다른 하나는 서가 라벨을 출력해요.

Rust가 “먼저 정의된 트레이트”처럼 숨은 규칙으로 하나를 고를 수도 있었을 거예요. 하지만 그러면 트레이트를 하나 추가하거나 use 두 줄의 순서만 바꿔도 프로그램이 출력하는 내용이 아무 말 없이 바뀌어 버려요. Rust는 추측하지 않고, 코드를 읽는 모두가 볼 수 있도록 코드 안에 한 번 적어 달라고 해요.

예외가 하나 있어요. 타입 자신의 impl Book 블록에 있는 메서드는 같은 이름의 트레이트 메서드보다 항상 우선해요. 그래서 그 경우에는 E0034가 나지 않아요. 트레이트와 고급 트레이트에서 더 읽어 볼 수 있어요.

방법 1: 트레이트를 통해 호출하기: Trait::method(&x)

섹션 제목: “방법 1: 트레이트를 통해 호출하기: Trait::method(&x)”

두 메서드가 다 필요하고, 매번 어느 쪽인지 말하고 싶을 때 이 방법을 고르세요. rustc가 제안하는 것과 똑같아요. 값은 첫 번째 인자로 넣고, 메서드의 &self가 기대하는 대로 &를 붙여요.

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn describe(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String {
format!("{} ({}쪽)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("BOOK: {}", self.title.to_uppercase())
}
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", Summary::describe(&book));
println!("{}", Label::describe(&book));
}

실행 결과:

Rust for Crabs (320쪽)
BOOK: RUST FOR CRABS
▶ 직접 실행해 보기

메서드가 &mut self를 받는다면 &mut book을 넘기세요. self를 받는다면 book을 넘기세요(점으로 호출할 때처럼 값이 이동해요).

방법 2: 완전 정규화 문법: <Type as Trait>::method()

섹션 제목: “방법 2: 완전 정규화 문법: <Type as Trait>::method()”

충돌하는 함수에 self가 없을 때 이 방법을 고르세요. 생성자나 fn category() 같은 함수요. 그러면 Rust가 살펴볼 값이 없으니까 Trait::category()만으로는 부족하고, 타입도 말해 줘야 해요. <Book as Summary>::category()는 “Book이 Summary를 구현해서 갖게 된 category“라고 읽어요. 메서드에도 쓸 수 있어요.

trait Summary {
fn category() -> String;
fn describe(&self) -> String;
}
trait Label {
fn category() -> String;
}
struct Book {
title: String,
}
impl Summary for Book {
fn category() -> String { String::from("논픽션") }
fn describe(&self) -> String { format!("제목이 {}인 책", self.title) }
}
impl Label for Book {
fn category() -> String { String::from("B 서가") }
}
fn main() {
let book = Book { title: String::from("Rust for Crabs") };
println!("{}", <Book as Summary>::category());
println!("{}", <Book as Label>::category());
println!("{}", <Book as Summary>::describe(&book));
}

실행 결과:

논픽션
B 서가
제목이 Rust for Crabs인 책
▶ 직접 실행해 보기

여기서 Book::category()라고 쓰면 역시 E0034가 나고, use fully-qualified syntax to disambiguate(완전 정규화 문법으로 명확히 하라)라는 help가 붙어요. Summary::category()만 쓰면 다른 에러인 E0790이 나요. 트레이트는 여전히 어느 타입을 말하는지 모르니까요.

방법 3: 메서드 하나의 이름 바꾸기

섹션 제목: “방법 3: 메서드 하나의 이름 바꾸기”

두 트레이트가 모두 내 것이고 이름이 겹친 게 우연일 때 이 방법을 고르세요. 하는 일이 다른 두 메서드라면 이름도 달라야 마땅하고, 그러면 짧은 book.describe()가 다시 동작해요. 가장 깔끔한 해결책일 때가 많아요.

trait Summary {
fn describe(&self) -> String;
}
trait Label {
fn label_text(&self) -> String;
}
struct Book {
title: String,
pages: u32,
}
impl Summary for Book {
fn describe(&self) -> String { format!("{} ({}쪽)", self.title, self.pages) }
}
impl Label for Book {
fn label_text(&self) -> String { format!("BOOK: {}", self.title.to_uppercase()) }
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", book.describe());
println!("{}", book.label_text());
}

실행 결과:

Rust for Crabs (320쪽)
BOOK: RUST FOR CRABS
▶ 직접 실행해 보기

물론 다른 크레이트에 있는 트레이트의 메서드 이름은 바꿀 수 없어요. 그럴 때는 방법 1이나 방법 4를 쓰세요.

방법 4: 필요한 트레이트만 가져오기

섹션 제목: “방법 4: 필요한 트레이트만 가져오기”

트레이트들이 서로 다른 모듈이나 크레이트에 있을 때 이 방법을 고르세요. 트레이트의 메서드는 그 트레이트가 스코프 안에 있을 때만 점 문법으로 쓸 수 있어요. 그러니 코드의 어떤 부분에서 하나만 필요하다면, 거기서는 그것만 가져오세요. use를 함수 안에 넣어서 그 함수에서만 적용되게 할 수도 있어요.

mod report {
pub trait Summary {
fn describe(&self) -> String;
}
}
mod shop {
pub trait Label {
fn describe(&self) -> String;
}
}
struct Book {
title: String,
}
impl report::Summary for Book {
fn describe(&self) -> String { format!("제목이 {}인 책", self.title) }
}
impl shop::Label for Book {
fn describe(&self) -> String { format!("BOOK: {}", self.title.to_uppercase()) }
}
fn print_label(book: &Book) {
use shop::Label; // 여기서는 Label만 스코프 안에 있어요
println!("{}", book.describe());
}
fn main() {
use report::Summary; // 여기서는 Summary만 스코프 안에 있어요
let book = Book { title: String::from("Rust for Crabs") };
println!("{}", book.describe());
print_label(&book);
}

실행 결과:

제목이 Rust for Crabs인 책
BOOK: RUST FOR CRABS
▶ 직접 실행해 보기

impl 블록에서는 report::Summary와 shop::Label을 전체 경로로 썼다는 점을 눈여겨보세요. 트레이트를 구현할 때는 use가 필요 없고, 점으로 호출할 때만 필요해요. use에 대해서는 모듈을 참고하세요.

직접 만든 to_string(). Display를 구현한 타입은 모두 표준 라이브러리의 ToString 트레이트에서 to_string()을 이미 받아요. 여기에 to_string 메서드를 가진 내 트레이트를 추가하면 point.to_string()이 E0034를 내고, candidate #2 is defined in an impl of the trait `ToString` for the type `T` (후보 #2는 T 타입에 대한 ToString 트레이트 구현에 정의돼 있다)가 나와요. 해결: 내 메서드의 이름을 바꾸거나(pretty(), to_label()), Pretty::to_string(&point)로 호출하세요.

prelude::*를 두 개 가져올 때. 라이브러리는 트레이트들을 가져오도록 use some_crate::prelude::*;를 제공하는 경우가 많아요. 같은 타입에 draw()나 render() 메서드를 추가하는 prelude 두 개를 가져오면 점 호출이 모호해져요. 해결: 글롭(*) 하나를 실제로 쓰는 항목만으로 바꾸거나, Render::draw(&data)로 호출하세요.

바운드가 두 개인 제네릭 함수. fn show<T: Summary + Label>(item: &T)에서 item.describe()는 E0034를 내요. 여기서는 rustc의 제안을 조심하세요. Label::describe(&item)을 제안하지만, item은 이미 참조자라서 그러면 E0277이 나요(the trait bound `&T: Label` is not satisfied, &T가 Label 트레이트 바운드를 만족하지 않는다). 남는 & 없이 Label::describe(item)이라고 쓰세요. 제네릭을 참고하세요.

  • E0790: Default::default()나 Summary::category()처럼 self가 없는 트레이트 함수를, 어느 타입인지 말하지 않고 호출했어요.
  • E0599: 반대 문제예요. 그 이름의 메서드를 하나도 찾지 못했어요. 트레이트를 가져오지 않은 경우가 많아요.
  • E0277: 트레이트 바운드를 만족하지 않아요. 예를 들어 Trait::method(&x)에 &를 하나 더 붙였을 때요.
  • E0428: 이름이 여러 번 정의됐어요. 트레이트 메서드 두 개가 아니라, 한 모듈 안에 같은 이름의 항목이 두 개 있는 경우예요.
  • 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.

이해도 점검

1. Book이 Summary와 Label을 구현하고, 둘 다 fn describe(&self)가 있어요. Label의 것을 호출하는 줄은 어느 것일까요?

2. 두 트레이트 모두 self가 없는 fn category() -> String이 있어요. 어느 호출이 동작할까요?

3. Book에는 자신의 impl Book { fn describe(&self) ... }가 있고, describe가 있는 Summary도 구현해요. book.describe()는 어떻게 될까요?

점수: 0 / 3