Rust 에러 E0284: type annotations needed (parse()의 변환 대상 타입)
E0284는 거의 항상 한 가지 뜻이에요. .parse()를 호출했는데, 텍스트를 무엇으로 파싱해야 할지 Rust가 모른다는 거예요. cannot satisfy `<_ as FromStr>::Err == _`라는 note는 무시무시해 보이지만, 해결책은 보통 : u32나 ::<u32> 같은 몇 글자면 돼요. 이 페이지에서는 고치는 방법을 먼저 보여 주고, 그다음 이 note를 하나씩 풀어서 더는 무섭지 않게 해 드릴게요. 이 시리즈의 다른 에러는 Rust 컴파일러 에러 해설에서 볼 수 있어요.
에러 메시지
섹션 제목: “에러 메시지”error[E0284]: type annotations needed --> src/main.rs:2:9 |2 | let age = "42".parse().unwrap(); | ^^^ ----- type must be known at this point | = note: cannot satisfy `<_ as FromStr>::Err == _`help: consider giving `age` an explicit type |2 | let age: /* Type */ = "42".parse().unwrap(); | ++++++++++++“type annotations needed”는 “타입 명시가 필요하다”는 뜻이에요. help는 “age에 명시적인 타입을 붙여 보라”고 제안해요.
이 에러를 내는 프로그램은 다음과 같아요.
fn main() { let age = "42".parse().unwrap(); println!("Next year you'll be {}", age + 1);}.unwrap() 대신 .expect(...)를 쓸 때도, match "42".parse() { ... }라고 쓸 때도, 클로저 안에서 .parse()를 쓸 때도 같은 note가 나와요. .parse()의 결과를 변수에 담지 않으면, rustc는 parse 자체를 가리키면서 parse::</* Type */>()를 제안해요.
무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”표시들은 쉬운 부분이에요.
age아래의^^^: 타입을 알 수 없는 변수예요.parse아래의-----: “type must be known at this point”, 즉 “이 시점에는 타입을 알아야 한다”예요. Rust가 막힌 곳이에요.- help: “consider giving
agean explicit type”, 즉 “age에 명시적인 타입을 붙여 보라”예요./* Type */을u32같은 진짜 타입으로 바꾸면 돼요.
이제 note를 조각조각 살펴볼게요. 컴파일러 메시지에서 _는 “아직 모르는 타입”이라는 뜻이에요.
| 조각 | 쉽게 말하면 |
|---|---|
<_ as FromStr> |
“파싱해서 만들 타입 (아직 몰라요)” |
::Err |
“…그리고 그 타입에 딸린 에러 타입” |
== _ |
“이것도 모르는 또 다른 타입과 같아야 해요” |
그러니까 note 전체는 이런 말이에요. “무엇으로 파싱할지 모르니까, parse가 어떤 종류의 에러를 반환할 수 있는지조차 알아낼 수 없어요.”
번역가에게 편지를 건네면서 “이거 번역해 주세요”라고만 말하는 것과 비슷해요. 프랑스어로? 일본어로? 말해 주기 전까지는 시작할 수 없고, 어떤 실수가 생길 수 있는지조차 알려 줄 수 없죠.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”parse의 시그니처를 조금 단순하게 쓰면 이래요.
fn parse<F: FromStr>(&self) -> Result<F, F::Err>F는 돌려받고 싶은 타입이고, F::Err는 그 타입의 에러 타입이에요. 대상 타입마다 자기 에러 타입을 갖고 있어요. i32 파싱은 ParseIntError로, f64는 ParseFloatError로, bool은 ParseBoolError로 실패할 수 있어요. Rust는 parse가 무엇을 반환하는지 알려면 F부터 알아야 해요.
이 선택은 실행할 때의 동작도 바꿔요. "42"는 u8로도, i32로도, f64로도 잘 파싱돼요. 하지만 "300"은 u8로는 실패하고(너무 커요), "4.5"는 i32로는 실패해요(정수가 아니에요). Rust는 추측하지 않아요. 잘못 추측하면 특정 입력에서만 드러나는 버그가 되기 때문이에요. FromStr이 어떻게 동작하는지, 직접 만든 타입에 어떻게 추가하는지는 변환 강좌에서 볼 수 있어요.
Rust가 타입을 스스로 알아내는 방식은 E0282 페이지에서 설명해요. “type annotations needed”에 왜 코드가 세 개(E0282, E0283, E0284)나 있는지는 E0283 페이지에서 설명해요. 짧게 말하면, E0284는 막힌 규칙이 모르는 타입에 딸린 타입(여기서는 F::Err)에 관한 것일 때 나오는 변형이에요. 여러분 입장에서는 고치는 법이 같아요. 타입을 알려 주세요.
고치는 방법
섹션 제목: “고치는 방법”방법 1: 변수에 타입 명시하기
섹션 제목: “방법 1: 변수에 타입 명시하기”대부분은 이 방법을 고르세요. Rust Book이 쓰는 스타일이고, 텍스트에 무엇이 들어 있어야 하는지도 코드에 기록돼요.
fn main() { let age: u32 = "42".parse().unwrap(); let price: f64 = "4.99".parse().unwrap(); let port: u16 = "8080".parse().expect("포트는 숫자여야 해요");
println!("내년이면 {}살이에요", age + 1); println!("커피 두 잔: {:.2}", price * 2.0); println!("{port}번 포트에서 기다리는 중");}실행 결과:
내년이면 43살이에요커피 두 잔: 9.988080번 포트에서 기다리는 중텍스트의 의미에 맞는 타입을 고르세요. 포트는 u16, 퍼센트는 u8, 가격은 f64처럼요. 너무 작은 타입을 고르면 올바른 입력도 에러가 되니까, 잘 모르겠다면 i64나 u32처럼 넉넉한 타입을 고르세요.
방법 2: 터보피시 parse::<T>() 쓰기
섹션 제목: “방법 2: 터보피시 parse::<T>() 쓰기”타입을 붙일 변수가 없을 때 이 방법을 고르세요. 클로저 안, 반복자 체인 중간, ... * 2 같은 식 안에서요.
fn main() { let line = "3 7 5"; let total = line .split(' ') .map(|s| s.parse::<u32>().unwrap()) .sum::<u32>(); let doubled = "21".parse::<i32>().unwrap() * 2;
println!("합계: {total}"); println!("두 배: {doubled}");}실행 결과:
합계: 15두 배: 42터보피시는 parse 자체에 붙이세요. 체인 안에서는 최종 결과에만 타입을 붙이는 걸로는 부족할 때가 많아요. 아래 “자주 보게 되는 곳”의 첫 번째 항목을 보세요.
방법 3: 타입을 정하고 에러를 제대로 처리하기
섹션 제목: “방법 3: 타입을 정하고 에러를 제대로 처리하기”파일, 명령줄 인자, 웹 폼처럼 진짜 입력을 다룰 때 이 방법을 고르세요. 이런 곳에서 .unwrap()을 쓰면 잘못된 텍스트 하나에 프로그램이 죽어요. parse에 타입을 붙이고, Result를 match로 처리하세요. 타입에 따라 어떤 입력이 성공하는지가 달라지는 걸 눈여겨보세요.
fn main() { for input in ["42", "300", "abc"] { match input.parse::<u8>() { Ok(n) => println!("{input}: 성공, {n}"), Err(e) => println!("{input}: {e}"), } }
let rate: Result<f64, _> = "0.25".parse(); println!("{:?}", rate);}실행 결과:
42: 성공, 42300: number too large to fit in target typeabc: invalid digit found in stringOk(0.25)에러 메시지는 Rust 표준 라이브러리가 출력하는 그대로라서 영어예요. “number too large to fit in target type”은 “숫자가 너무 커서 대상 타입에 들어가지 않는다”, “invalid digit found in string”은 “문자열에 잘못된 숫자가 있다”는 뜻이에요.
Result<f64, _>에서는 값의 타입만 정해요. _는 에러 타입(ParseFloatError)을 Rust가 채우게 하는데, 바로 note가 물어보던 부분이에요. 자세한 내용은 Result에 있어요.
방법 4: 타입이 정해진 곳에서 값 쓰기
섹션 제목: “방법 4: 타입이 정해진 곳에서 값 쓰기”파싱한 값을 타입이 정해진 구조체 필드나 함수 매개변수에 넘길 때 이 방법을 고르세요. Rust는 함수 전체를 읽으니까, 나중에 쓰는 곳이 질문에 답해 줘요.
struct Config { port: u16, workers: u8,}
fn greet(age: u32) { println!("환영해요! {age}살이시군요.");}
fn main() { let port = "8080".parse().unwrap(); // Config에서 u16으로 정해져요 let workers = "4".parse().unwrap(); // Config에서 u8로 정해져요 let config = Config { port, workers }; println!("포트 {}, 워커 {}개", config.port, config.workers);
let age = "42".parse().unwrap(); // greet에서 u32로 정해져요 greet(age);}실행 결과:
포트 8080, 워커 4개환영해요! 42살이시군요.모든 사용이 단서가 되는 건 아니에요. 그래서 이게 방법 4예요. age + 1 같은 계산이나 age >= limit 같은 비교는 limit이 u32여도 여전히 E0284를 내요. 연산자는 여러 타입 조합에서 동작하니까, 정확한 타입 하나를 알려 주지 못하거든요. 그리고 age.pow(2)처럼 메서드를 호출하면 에러가 E0282로 바뀌어요. 잘 모르겠다면 방법 1을 쓰세요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”map 안에서 파싱한 뒤 sum()할 때. let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum();은 total에 타입이 있는데도 E0284예요. u32는 u32 값들의 합일 수도, &u32 값들의 합일 수도 있어서, 이 명시만으로는 각 항목이 무엇인지 Rust가 알 수 없어요. 해결: s.parse::<u32>(). (Vec<i32>로 collect할 때는 통해요. Vec<i32>에는 i32만 담을 수 있으니까요.)
파싱 결과를 Result로 collect할 때. let nums: Result<Vec<_>, _> = text.split(' ').map(|x| x.parse()).collect();는 E0284예요. Vec<_>의 _가 항목 타입을 열어 두기 때문이에요. 해결: Result<Vec<i32>, _>. 이 편리한 패턴은 처음 나온 잘못된 항목에서 멈추고, 그 에러를 돌려줘요.
사용자 입력을 읽을 때. 숫자 맞히기 게임이나 명령줄 도구의 let guess = line.trim().parse().unwrap(); 같은 곳이에요. 해결: Rust Book과 똑같이 let guess: u32 = ...라고 쓰세요.
?를 쓰면 더 이상한 에러가 나요. let port = text.parse()?;는 E0284를 내지 않아요. 에디션 2021에서는 Rust가 () 타입으로 대체하고 E0277을 보고해요. the trait bound `(): FromStr` is not satisfied(()가 FromStr을 구현하지 않는다)예요(에디션 2024에서는 () 대신 !라고 나와요). 고치는 법은 같아요. let port: u16 = text.parse()?;.
관련 에러
섹션 제목: “관련 에러”- E0282: type annotations needed. 단서가 전혀 없을 때(또는
age.pow(2)같은 메서드 호출에 타입이 필요할 때) 나와요. - E0283:
.sum(),.collect(),.into()뒤에 여러 타입이 들어맞을 때의 type annotations needed예요. 왜 코드가 세 개인지도 설명해요. - E0790:
FromStr::from_str("42")나Default::default()를 어떤 타입인지 말하지 않고 호출했어요. - E0277: 타입 없이
.parse()?를 쓰면 나오는 에러이고, 고른 타입이 텍스트로부터 파싱될 수 없을 때도 나와요. - E0599: 메서드를 찾을 수 없어요. 예를 들어
u32변수처럼 텍스트가 아닌 것에.parse()를 호출했을 때예요(그럴 때는u8::try_from(n)으로 변환하세요). - 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. cannot satisfy <_ as FromStr>::Err == _를 쉽게 말하면 무슨 뜻일까요?
2. let port = "8080".parse().unwrap();를 고치는 줄은 무엇일까요?
3. let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum();도 E0284를 내요. 타입은 어디에 붙여야 할까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0284 — Rust error codes index (영어): 이 에러의 공식 설명이에요.
str::parse— 표준 라이브러리 문서 (영어): 메서드의 시그니처와, 타입 명시 방식과 터보피시 방식을 모두 보여 줘요.FromStr— 표준 라이브러리 문서 (영어):parse뒤에 있는 트레이트예요.Err타입과 직접 구현하는 법도 나와요.- 추리 게임 — The Rust Programming Language 한국어판:
let guess: u32 = guess.trim().parse()가 처음 나오는 곳이에요. 한 단계씩 설명해요. - 데이터 타입 — The Rust Programming Language 한국어판:
"42".parse()에 왜 타입 명시가 필요한지, 실제 컴파일러 에러와 함께 설명해요.