콘텐츠로 이동

Rust 에러 E0252: the name is defined multiple times (use 두 개의 이름 충돌)

E0252는 use 줄 두 개가 같은 이름을 가져온다는 뜻이에요. 교과서 같은 예는 use std::fmt::Result;와 use std::io::Result;예요. 서로 다른 두 타입이 둘 다 Result라는 이름을 갖고 있어요. fmt::Error와 io::Error, 두 Write 트레이트도 같은 경우예요. 아주 흔한 에러이고, 보통 use 줄 한두 개만 바꾸면 쉽게 고칠 수 있어요. rustc 1.97도 여전히 이 에러를 자기만의 코드인 E0252로 알려 줘요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.

error[E0252]: the name `Result` is defined multiple times
--> src/main.rs:2:5
|
1 | use std::fmt::Result;
| ---------------- previous import of the type `Result` here
2 | use std::io::Result; // ❌ 두 번째 `Result`
| ^^^^^^^^^^^^^^^ `Result` reimported here
|
= note: `Result` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
|
2 | use std::io::Result as OtherResult; // ❌ 두 번째 `Result`
| ++++++++++++++

“the name Result is defined multiple times”는 “Result라는 이름이 여러 번 정의됐다”는 뜻이에요. 각 라벨은 아래에서 풀어 볼게요.

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

use std::fmt::Result;
use std::io::Result; // ❌ 두 번째 `Result`
struct Temperature {
celsius: i32,
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> Result<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(std::io::Error::other("숫자가 아니에요")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("에러: {}", e),
}
}

rustc는 이 에러 뒤에 에러를 네 개 더 출력해요. E0107(“type alias takes 0 generic arguments”, 타입 별칭은 제네릭 인자를 받지 않는다), 타입이 맞지 않는다는 E0308 두 개, 그리고 E0277이에요. 이것들을 쫓아가지 마세요. Rust가 첫 번째 use를 남겼기 때문에 파일 안의 모든 Result가 이제 fmt::Result를 뜻하고, 그래서 parse_temperature가 말이 안 되게 된 거예요. E0252를 고치면 모두 사라져요.

rustc는 use 줄 두 개를 가리켜요.

  1. --- 줄(1번째 줄): “previous import of the type Result here”(타입 Result를 이미 여기서 가져왔다). 이쪽이 먼저 나왔으니 Rust가 남기는 쪽이에요.
  2. ^^^ 줄(2번째 줄): “Result reimported here”(Result를 여기서 다시 가져왔다). 두 번째 use가 같은 이름을 차지하려고 해요.
  3. help: 줄은 두 번째 use의 이름을 as로 바꾸라고 제안해요.

std::fmt::Result와 std::io::Result는 정말 다른 타입이에요. fmt::Result는 그냥 Result<(), fmt::Error>이고, Display와 Debug에서 써요. io::Result<T>는 Result<T, io::Error>이고, 파일과 네트워크에서 써요. 이름은 같지만 하는 일이 달라요.

휴대폰 연락처에 “엄마”를 두 명 저장한 것과 비슷해요. 내 어머니와 배우자의 어머니요. “엄마”를 누르면 휴대폰은 누구에게 걸지 알아야 해요. 그래서 한쪽에 단어를 덧붙이거나, “엄마(우리 집)“와 “엄마(처가)“처럼 저장하죠.

한 모듈 안에서 이름 하나는 정확히 하나의 항목을 가리켜요. use 줄은 아무것도 복사하지 않아요. 다른 곳에 있는 항목에 짧은 이름을 하나 붙여 줄 뿐이에요. 두 use 줄이 같은 짧은 이름을 붙이면 그 뒤의 모든 Result가 모호해지니까, Rust는 쓰는 곳마다 짐작하는 대신 use에서 멈춰요.

E0252는 첫 줄이 같은 세 형제 중 하나예요. E0255는 use 줄과 내가 만든 항목의 충돌이고, E0428은 내가 만든 항목 두 개의 충돌이에요. 셋 모두 “한 모듈 안에서는 이름 하나에 하나만”이라는 같은 규칙에서 나와요. 모듈을 참고하세요.

방법 1: 모듈을 가져와서 fmt::와 io::로 쓰기

섹션 제목: “방법 1: 모듈을 가져와서 fmt::와 io::로 쓰기”

대부분은 이 방법을 고르세요. Result와 Error라면 언제나요. 부모 모듈을 가져오고, 앞에 모듈 이름을 쓰세요. 그러면 쓰는 곳마다 어느 Result인지 드러나요. The Rust Book도 바로 이런 경우에 이 스타일을 권해요.

use std::fmt;
use std::io;
struct Temperature {
celsius: i32,
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> io::Result<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(io::Error::other("숫자가 아니에요")),
}
}
fn main() {
for input in ["21", " -4 ", "warm"] {
match parse_temperature(input) {
Ok(t) => println!("{}", t),
Err(e) => println!("에러: {}", e),
}
}
}

실행 결과:

21 °C
-4 °C
에러: 숫자가 아니에요
▶ 직접 실행해 보기

덤으로, 프렐루드(prelude)에 있는 평범한 Result<T, E>도 따로 가져오지 않고 그대로 쓸 수 있어요.

두 타입 중 하나를 아주 자주 써서 짧은 이름을 붙이고 싶을 때 이 방법을 고르세요. rustc가 제안하는 방법이에요. OtherResult보다는 IoResult처럼 어디서 왔는지 알려 주는 이름을 고르세요.

use std::fmt::Result;
use std::io::Result as IoResult;
struct Temperature {
celsius: i32,
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> IoResult<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(std::io::Error::other("숫자가 아니에요")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("에러: {}", e),
}
}

실행 결과:

21 °C
▶ 직접 실행해 보기

그냥 Result라고 써 놓고 실은 fmt::Result를 뜻하면, 다음에 코드를 읽는 사람이 놀랄 수 있어요. 그래서 보통은 방법 1이 더 분명해요. as는 use std::collections::BTreeMap as Map;처럼 이름이 길 때 빛을 발해요.

방법 3: 트레이트를 as _로 가져오기

섹션 제목: “방법 3: 트레이트를 as _로 가져오기”

메서드를 부르려고만 가져오는 트레이트라면 이 방법을 고르세요. 두 Write 트레이트가 대표적이에요. fmt::Write는 String에 write!할 수 있게 해 주고, io::Write는 표준 출력이나 파일에 write!할 수 있게 해 줘요. 둘 다 이름으로 가져오면 충돌해요.

error[E0252]: the name `Write` is defined multiple times
--> src/main.rs:2:5
|
1 | use std::fmt::Write;
| --------------- previous import of the trait `Write` here
2 | use std::io::Write; // ❌ 두 번째 `Write`
| ^^^^^^^^^^^^^^ `Write` reimported here
|
= note: `Write` must be defined only once in the type namespace of this module
help: you can use `as` to change the binding name of the import
|
2 | use std::io::Write as OtherWrite; // ❌ 두 번째 `Write`
| +++++++++++++

라벨은 “트레이트 Write를 이미 여기서 가져왔다”와 “Write를 여기서 다시 가져왔다”는 뜻이에요. 문제가 되는 프로그램은 이래요.

use std::fmt::Write;
use std::io::Write; // ❌ 두 번째 `Write`
fn main() {
let mut report = String::new();
write!(report, "테스트 {}개 통과", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

여기서는 Write라는 이름을 직접 쓰는 곳이 없어요. 메서드만 있으면 돼요. as _는 트레이트를 이름 없이 가져와서, 충돌할 이름이 아예 없고 두 트레이트의 메서드는 모두 그대로 동작해요.

use std::fmt::Write as _;
use std::io::Write as _;
fn main() {
let mut report = String::new();
write!(report, "테스트 {}개 통과", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

실행 결과:

테스트 12개 통과
▶ 직접 실행해 보기

fn log(w: &mut impl io::Write)처럼 둘 중 하나의 이름을 써야 한다면 방법 1과 함께 쓰세요. use std::io;라고 쓰고 io::Write로 쓰면 돼요. 트레이트를 참고하세요.

방법 4: 같은 항목을 두 번 가져온 줄 지우기

섹션 제목: “방법 4: 같은 항목을 두 번 가져온 줄 지우기”

두 줄이 완전히 같은 것을 가져올 때 이 방법을 고르세요. 코드를 합치고 나서, 또는 이미 있던 use를 에디터가 또 넣었을 때 자주 생겨요.

error[E0252]: the name `HashMap` is defined multiple times
--> src/main.rs:2:24
|
1 | use std::collections::HashMap;
| ------------------------- previous import of the type `HashMap` here
2 | use std::collections::{HashMap, HashSet}; // ❌ `HashMap`을 또 가져와요
| ^^^^^^^--
| |
| `HashMap` reimported here
| help: remove unnecessary import

문제가 되는 프로그램은 이래요.

use std::collections::HashMap;
use std::collections::{HashMap, HashSet}; // ❌ `HashMap`을 또 가져와요
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("apples", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("pears");
println!("사과: {}개, 품절: {}개", stock["apples"], sold_out.len());
}

help가 다르다는 점을 눈여겨보세요. as가 아니라 “remove unnecessary import”(필요 없는 가져오기를 지워라)예요. 한 줄만 남기세요.

use std::collections::{HashMap, HashSet};
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("apples", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("pears");
println!("사과: {}개, 품절: {}개", stock["apples"], sold_out.len());
}

실행 결과:

사과: 3개, 품절: 1개
▶ 직접 실행해 보기

해시맵과 HashSet을 참고하세요.

두 곳에서 온 Error. use std::fmt::Error;와 use std::io::Error;, 또는 use std::error::Error; 옆의 use anyhow::Error;예요. 해결: use std::io;라고 쓰고 io::Error로 쓰거나, use anyhow::Error as AnyError;라고 쓰세요. anyhow와 thiserror를 참고하세요.

타입 이름이 같은 두 크레이트. 날짜를 다루는 프로젝트에서는 use chrono::Duration;과 use std::time::Duration;이 흔한 조합이에요. 해결: 한쪽만 짧은 이름으로 두고, 다른 쪽은 chrono::Duration처럼 모듈 이름을 붙여 쓰세요. 날짜와 시간을 참고하세요.

글롭 가져오기. use std::fmt::*;와 use std::io::*;를 함께 쓰면 use 줄에서는 E0252가 나지 않아요. 문제는 나중에, 처음 Result를 쓰는 곳에서 코드 없는 에러로 나타나요. error: `Result` is ambiguous(Result가 모호하다)예요. 해결: 방법 1처럼 필요한 이름을 직접 가져오세요.

  • E0255: use 줄이 이름이 같은 내가 만든 구조체, 열거형, 함수와 충돌해요.
  • E0428: 내가 만든 항목 두 개가 같은 이름을 써요. 함수를 오버로딩하려고 할 때 자주 나요.
  • E0432: 해결되지 않은 가져오기예요. use 줄이 존재하지 않는 것을 가리켜요.
  • E0034: 스코프 안의 두 트레이트가 한 타입에 같은 이름의 메서드를 줘요.
  • E0308: 타입이 맞지 않아요. Result가 충돌한 뒤에 따라 나오는 에러 중 하나예요.
  • 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.

이해도 점검

1. 한 파일에서 fmt::Result와 io::Result가 모두 필요해요. 보통 가장 깔끔한 use는 무엇일까요?

2. std::fmt::Write와 std::io::Write의 메서드가 모두 필요하지만, 트레이트 이름을 직접 쓰지는 않아요. 무엇이 통할까요?

3. rustc가 HashMap is reimported here라고 하면서 remove unnecessary import를 제안해요. 무슨 일이 있었던 걸까요?

점수: 0 / 3