Rust 에러 E0255: the name is defined multiple times (use와 내 항목의 이름 충돌)
E0255는 use 줄로 가져온 이름과 내가 직접 정의한 것의 이름이 같다는 뜻이에요. 대표적인 경우는 이래요. 파일 맨 위에서 std::io::Error를 가져오고, 아래에서 나만의 enum Error를 만드는 거예요. Rust는 이름 하나가 두 가지를 뜻하게 둘 수 없어서 컴파일을 멈춰요. 나만의 에러 타입을 처음 만들 때 아주 흔한 에러이고, 고치는 데는 짧은 한 줄이면 돼요. E0412와 달리 이 코드는 최근 버전에서도 바뀌지 않았어요. rustc 1.97도 여전히 E0255로 알려 줘요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.
에러 메시지
섹션 제목: “에러 메시지”error[E0255]: the name `Error` is defined multiple times --> src/main.rs:3:1 |1 | use std::io::Error; | -------------- previous import of the type `Error` here2 |3 | enum Error { // ❌ 1번째 줄에서 가져온 것과 같은 이름 | ^^^^^^^^^^ `Error` redefined here | = note: `Error` must be defined only once in the type namespace of this modulehelp: you can use `as` to change the binding name of the import |1 | use std::io::Error as OtherError; | +++++++++++++
warning: unused import: `std::io::Error` --> src/main.rs:1:5 |1 | use std::io::Error; | ^^^^^^^^^^^^^^“the name Error is defined multiple times”는 “Error라는 이름이 여러 번 정의됐다”는 뜻이에요. 아래쪽 warning: unused import는 “가져온 std::io::Error를 쓰지 않는다”는 경고예요. 각 부분은 아래에서 풀어 볼게요.
이 에러를 내는 프로그램은 다음과 같아요.
use std::io::Error;
enum Error { // ❌ 1번째 줄에서 가져온 것과 같은 이름 EmptyName, TooLong(usize),}
fn check_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(Error::EmptyName); } if name.len() > 10 { return Err(Error::TooLong(name.len())); } Ok(())}
fn main() { match check_name("") { Ok(()) => println!("이름이 괜찮아요"), Err(Error::EmptyName) => println!("이름이 비어 있어요"), Err(Error::TooLong(n)) => println!("이름이 {}글자예요. 최대 10글자까지 돼요", n), }}무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”rustc는 세 가지를 가리켜요.
---줄(1번째 줄): “previous import of the typeErrorhere”(타입Error를 이미 여기서 가져왔다).use줄이 이 모듈에Error라는 이름을 이미 넣어 뒀어요.^^^줄(3번째 줄): “Errorredefined here”(Error가 여기서 다시 정의됐다). 내가 만든enum Error가 같은 이름을 차지하려고 해요.help:줄은 빠져나갈 길을 알려 줘요. “you can useasto change the binding name of the import”, 즉as로 가져온 것에 다른 이름을 붙이라는 거예요.
그 아래의 warning: unused import는 쓸모 있는 힌트예요. 코드 어디에서도 std::io::Error를 실제로 쓰지 않는다는 뜻이에요(이 프로그램의 Error는 모두 내 열거형을 가리켜요). 이럴 때는 1번째 줄을 그냥 지우면 돼요. 둘 다 필요하다면 계속 읽어 주세요.
note에는 타입 네임스페이스(type namespace)라는 말이 나와요. 타입, 트레이트, 모듈은 이름 목록 하나를 같이 쓰고, 함수, 상수, static은 다른 목록을 써요. 그래서 use std::mem::swap;과 내가 만든 fn swap도 같은 에러가 나요. 다만 이때는 “값 네임스페이스”(value namespace)라고 나와요.
한 반에 이름이 “민준”인 학생이 두 명 있다고 생각해 보세요. 선생님은 이제 “민준아”라고만 부를 수 없어요. 누군가에게 “큰 민준”처럼 별명이 필요해요. as가 가져온 이름에 붙여 주는 게 바로 그 별명이에요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”모듈 안의 이름 하나는 정확히 하나의 항목을 가리켜요. Rust가 10번째 줄의 Error::EmptyName을 읽을 때는 어느 Error인지 알아야 하는데, Rust는 짐작하지 않아요. 몰래 내 열거형을 고르면 use 줄이 아무 일도 하지 않게 되고, io::Error를 고르면 내 열거형을 쓸 수 없게 돼요. 어느 쪽이든 조용히 고르면 실수가 숨어 버려요.
그래서 E0255는 E0428(내가 만든 항목 두 개가 같은 이름)과 E0252(use 줄 두 개가 같은 이름)의 형제 에러예요. 규칙은 같아요. 한 모듈 안에서는 이름 하나에 하나만. use가 어떻게 동작하는지는 모듈을 참고하세요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: as로 가져온 이름 바꾸기
섹션 제목: “방법 1: as로 가져온 이름 바꾸기”한 파일에서 두 타입이 모두 필요할 때 이 방법을 고르세요. 꽤 흔한 상황이에요. 앱의 규칙에는 내 Error를, 파일과 네트워크에는 io::Error를 쓰는 식이죠. 가져온 것에 어디서 왔는지 알려 주는 별명을 붙이세요. rustc의 자리 표시자 OtherError보다 IoError가 더 잘 읽혀요.
use std::io::Error as IoError;use std::io::ErrorKind;
#[derive(Debug)]enum Error { EmptyName, TooLong(usize),}
fn check_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(Error::EmptyName); } if name.len() > 10 { return Err(Error::TooLong(name.len())); } Ok(())}
fn save_name(name: &str) -> Result<(), IoError> { if name == "root" { return Err(IoError::new(ErrorKind::PermissionDenied, "예약된 이름이에요")); } Ok(())}
fn main() { println!("{:?}", check_name("Ferris")); println!("{:?}", check_name("")); println!("{:?}", check_name("Ferris the Crab")); match save_name("root") { Ok(()) => println!("저장했어요"), Err(e) => println!("io 에러: {}", e), }}실행 결과:
Ok(())Err(EmptyName)Err(TooLong(15))io 에러: 예약된 이름이에요check_name에 넘기는 이름은 일부러 영어로 뒀어요. len()은 바이트 수를 세는데, 한글은 한 글자가 3바이트라서 결과가 달라지거든요. as는 이 파일 안에서만 이름을 바꿔요. 다른 곳에서는 여전히 std::io::Error예요.
방법 2: 타입 대신 모듈 가져오기
섹션 제목: “방법 2: 타입 대신 모듈 가져오기”Error를 쓸 때마다 어디서 온 것인지 보이게 하고 싶을 때 이 방법을 고르세요. 부모 모듈을 use std::io;로 가져오고 io::Error라고 쓰세요. 그러면 내 Error는 짧은 이름을 그대로 쓸 수 있어요. Error나 Result처럼 흔한 이름의 타입에는 많은 Rust 프로그래머가 이 스타일을 좋아해요.
use std::io;
enum Error { EmptyName,}
fn check_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(Error::EmptyName); } Ok(())}
fn save_name(name: &str) -> Result<(), io::Error> { if name == "root" { return Err(io::Error::new(io::ErrorKind::PermissionDenied, "예약된 이름")); } Ok(())}
fn main() { if let Err(Error::EmptyName) = check_name("") { println!("이름이 비어 있어요"); } if let Err(e) = save_name("root") { println!("io 에러: {}", e); }}실행 결과:
이름이 비어 있어요io 에러: 예약된 이름방법 3: 내가 만든 항목의 이름 바꾸기
섹션 제목: “방법 3: 내가 만든 항목의 이름 바꾸기”내 타입을 여러 파일에서 쓰거나 다른 사람도 쓸 때 이 방법을 고르세요. 두 번째 Error보다는 NameError나 AppError처럼 구체적인 이름이 더 분명하고, use 줄은 그대로 둘 수 있어요.
use std::io::Error;
#[derive(Debug)]enum NameError { EmptyName, TooLong(usize),}
fn check_name(name: &str) -> Result<(), NameError> { if name.is_empty() { return Err(NameError::EmptyName); } if name.len() > 10 { return Err(NameError::TooLong(name.len())); } Ok(())}
fn main() { println!("{:?}", check_name("Ferris the Crab")); let disk = Error::other("디스크가 가득 찼어요"); println!("{}", disk);}실행 결과:
Err(TooLong(15))디스크가 가득 찼어요프로젝트가 커지면 thiserror 크레이트로 이런 에러 열거형을 훨씬 짧게 쓸 수 있어요. 사용자 정의 에러를 참고하세요.
방법 4: 쓰지 않는 use 지우기
섹션 제목: “방법 4: 쓰지 않는 use 지우기”위 에러처럼 rustc가 warning: unused import도 함께 출력할 때 이 방법을 고르세요. 예전 코드에서 남았거나, 에디터가 자동으로 넣은 use예요. 그 줄을 지우면 이 페이지 맨 위의 프로그램이 그대로 동작해요.
enum Error { EmptyName, TooLong(usize),}
fn check_name(name: &str) -> Result<(), Error> { if name.is_empty() { return Err(Error::EmptyName); } if name.len() > 10 { return Err(Error::TooLong(name.len())); } Ok(())}
fn main() { match check_name("") { Ok(()) => println!("이름이 괜찮아요"), Err(Error::EmptyName) => println!("이름이 비어 있어요"), Err(Error::TooLong(n)) => println!("이름이 {}글자예요. 최대 10글자까지 돼요", n), }}실행 결과:
이름이 비어 있어요자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”표준 라이브러리와 이름이 같은 내 함수. use std::mem::swap;과 fn swap(a: &mut i32, b: &mut i32)를 함께 쓰면 E0255가 나요. 라벨은 “previous import of the value swap here”(값 swap을 이미 여기서 가져왔다)이고, note에는 “value namespace”(값 네임스페이스)라고 나와요. 해결: 내 함수를 swap_values라고 부르거나, 직접 만들 생각이었다면 use를 지우세요.
모듈 사이에서 복사해 붙여넣기. struct User를 mod models로 옮기고 맨 위에 use models::User;를 추가했는데, main.rs에 있던 예전 struct User를 지우는 걸 깜빡한 경우예요. 해결: 예전 복사본을 지워서 User가 models에 있는 것만 가리키게 하세요.
표준 라이브러리와 이름이 같은 트레이트. use std::fmt::Display;와 내가 만든 trait Display도 똑같이 충돌해요(“previous import of the trait Display”, 트레이트 Display를 이미 가져왔다). 해결: 내 트레이트의 이름을 바꾸거나(Describe, Render), use std::fmt;라고 쓰고 fmt::Display로 쓰세요.
알아 두면 좋은 점: use std::io::*; 같은 글롭(glob) 가져오기는 E0255를 절대 내지 않아요. *로 들어온 이름보다 내가 만든 항목이 조용히 이겨요. 편하긴 하지만 어느 Error를 쓰는지 가려질 수도 있어서, 보통은 이름을 하나씩 가져오는 편이 더 분명해요.
관련 에러
섹션 제목: “관련 에러”- E0252:
fmt::Result와io::Result처럼use줄 두 개가 같은 이름을 가져와요. - E0428: 내가 만든 항목 두 개가 같은 이름을 써요. 함수를 오버로딩하려고 할 때 자주 나요.
- E0592:
impl블록 안에 이름이 같은 메서드가 두 개 있어요. - E0432:
use줄 자체가 존재하지 않는 것을 가리켜요. - E0425: 반대 문제예요. 쓰는 곳에서 그 이름이 아예 정의되지 않았어요.
- 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 파일이 use std::fmt::Result;로 시작하고, 아래에서 enum Result { Pass, Fail }을 정의해요. 어떤 에러가 날까요?
2. 한 파일에서 std::io::Error와 내가 만든 enum Error가 모두 필요해요. 깔끔한 해결 방법은 무엇일까요?
3. rustc가 E0255와 함께 warning: unused import: std::io::Error도 보여 줘요. 이 경고는 무엇을 알려 줄까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0255 — Rust error codes index (영어):
use bar::foo;가fn foo와 충돌하는 예제를as나 부모 경로로 고치는 공식 설명이에요. - as 키워드로 새로운 이름 제공하기 — The Rust Programming Language 한국어판: 두
Result타입을 예로 들어as로 가져온 이름을 바꾸는 법을 보여 줘요. - Use declarations — The Rust Reference (영어):
as, 묶어서 가져오기, 글롭까지use의 모든 형태를 설명해요. - Namespaces — The Rust Reference (영어): 에러의 note에 나오는 타입 네임스페이스와 값 네임스페이스예요.
- The
usedeclaration — Rust by Example (영어): 가져오기와 이름 바꾸기를 짧고 실행 가능한 예제로 보여 줘요.