Rust 에러 E0308: mismatched types (타입 불일치)
E0308은 Rust는 어떤 타입의 값을 기대했는데, 실제로는 다른 타입의 값을 받았다는 뜻이에요. Rust에서 아마 가장 자주 보게 될 에러예요. 다행히 에러 메시지가 거의 항상 두 타입을 모두 알려 주기 때문에 보통 금방 고칠 수 있어요. (다른 에러를 찾고 있다면 Rust 컴파일러 에러 해설을 보세요.)
에러 메시지
섹션 제목: “에러 메시지”error[E0308]: mismatched types --> src/main.rs:6:29 |6 | let user = User { name: "Ferris" }; | ^^^^^^^^ expected `String`, found `&str` |help: try using a conversion method |6 | let user = User { name: "Ferris".to_string() }; | ++++++++++++“mismatched types”는 “타입이 맞지 않는다”는 뜻이에요. 라벨은 “String을 기대했는데 &str이 왔다”고 말하고, help는 “변환 메서드를 써 보라”며 .to_string()을 제안해요.
이 에러를 내는 프로그램은 다음과 같아요.
struct User { name: String,}
fn main() { let user = User { name: "Ferris" }; println!("{}", user.name);}무슨 뜻인가요
섹션 제목: “무슨 뜻인가요”E0308 메시지는 언제나 같은 모양이에요. expected A, found B, 즉 “A를 기대했는데 B가 왔다”예요.
- expected(기대한 타입)는 그 자리에 Rust가 필요로 하는 타입이에요. 이 타입은 다른 곳에서 정해져요. 타입 명시, 함수의 매개변수나 반환 타입, 구조체 필드, 또는
if/match의 다른 갈래 같은 곳이요. - found(실제 타입)는 내가 그 자리에 실제로 쓴 값의 타입이에요.
예제에서 name 필드는 String으로 선언되어 있으니 Rust는 String을 기대해요. 그런데 "Ferris"는 문자열 리터럴이고, 타입은 &str이에요. 둘은 서로 관련이 있지만 같은 타입은 아니에요.
help: 줄도 꼭 읽어 보세요. E0308에서는 컴파일러가 위의 .to_string()처럼 정확한 해결책을 알려 줄 때가 많아요.
Rust가 이렇게 하는 이유
섹션 제목: “Rust가 이렇게 하는 이유”Rust는 타입을 몰래 바꾸지 않아요. 어떤 언어에서는 숫자가 슬그머니 문자열이 되기도 하고, 큰 숫자가 작은 타입에 맞게 슬쩍 잘리기도 해요. 이런 숨은 변환은 진짜 버그를 만들어요. 데이터가 사라지고, 결과가 틀리고, 실제 실수와 한참 떨어진 곳에서 프로그램이 죽어요.
그래서 Rust는 모든 변환을 코드에 드러나게 써야 해요. 타이핑은 조금 늘지만, 나중에 코드를 읽을 때 값의 타입이 어디서 바뀌는지, 그 단계가 실패할 수 있는지 한눈에 알 수 있어요. 타입 자체는 데이터 타입에서 설명해요.
고치는 방법
섹션 제목: “고치는 방법”아래의 각 방법은 에러의 모양부터 보여 줘요. 내 에러와 같은 모양을 찾아서 바로 넘어가면 돼요.
방법 1: 끝의 세미콜론 지우기
섹션 제목: “방법 1: 끝의 세미콜론 지우기”error[E0308]: mismatched types --> src/main.rs:1:46 |1 | fn total_price(price: u32, quantity: u32) -> u32 { | ----------- ^^^ expected `u32`, found `()` | | | implicitly returns `()` as its body has no tail or `return` expression2 | price * quantity; | - help: remove this semicolon to return this value컴파일러는 “본문에 마지막 표현식도 return도 없어서 암묵적으로 ()를 반환한다”고 말하고, “이 값을 반환하려면 세미콜론을 지우라”고 알려 줘요.
에러 메시지에 found () 부분이 보이면 함수의 마지막 줄을 확인하세요. Rust에서는 블록의 마지막 표현식에 세미콜론이 없으면 그게 반환값이 돼요. ;를 붙이면 문(statement)이 되어 버려서, 함수는 대신 ()(아무것도 없음을 나타내는 타입)를 반환해요. 세미콜론을 지우면 돼요.
fn total_price(price: u32, quantity: u32) -> u32 { price * quantity // 세미콜론 없음: 이 값이 반환돼요}
fn main() { println!("합계: {}", total_price(250, 3));}실행 결과:
합계: 750if 블록과 match 갈래 안에서도 똑같은 일이 일어나요. 자세한 내용은 함수에 있어요.
방법 2: String과 &str 사이에서 변환하기
섹션 제목: “방법 2: String과 &str 사이에서 변환하기”String은 소유하는, 늘어날 수 있는 문자열이에요. &str은 어떤 텍스트를 빌려서 보는 것이고요. 자세한 이야기는 문자열에 있어요. 변환은 양쪽 모두 짧아요.
&str에서String으로:.to_string()또는String::from(...).String에서&str으로:&로 빌리기(또는.as_str()호출).
두 번째 경우는 expected &str, found String 형태로 나타나요. 보통 &str을 받는 함수에 String을 넘길 때 나와요.
struct User { name: String,}
fn greet(name: &str) { println!("안녕하세요, {name}님!");}
fn main() { // &str -> String: .to_string() 또는 String::from() let user = User { name: "Ferris".to_string() };
// String -> &str: &로 빌리기 (또는 .as_str() 호출) greet(&user.name); greet(user.name.as_str());
let mut tags: Vec<String> = Vec::new(); tags.push(String::from("rust")); println!("{tags:?}");}실행 결과:
안녕하세요, Ferris님!안녕하세요, Ferris님!["rust"]방법 3: 숫자 타입 사이에서 변환하기
섹션 제목: “방법 3: 숫자 타입 사이에서 변환하기”error[E0308]: mismatched types --> src/main.rs:3:22 |3 | let count: i32 = names.len(); | --- ^^^^^^^^^^^ expected `i32`, found `usize` | | | expected due to this“expected due to this”는 “이것 때문에 이 타입을 기대했다”는 뜻으로, 기대한 타입이 어디서 왔는지 가리켜요.
i32, i64, u32, usize는 모두 다른 타입이고, Rust는 이들을 섞어 쓰지 않아요. 길이와 인덱스는 항상 usize예요. 다음 중 하나로 변환하세요.
.into()(또는i64::from(x)):i32에서i64처럼 데이터를 절대 잃지 않는 변환일 때.i64::try_from(x)(또는.try_into()): 값이 안 들어갈 수도 있을 때.Result를 반환하니까 실패하면 어떻게 할지 내가 정해요.as: 빠른 형 변환이에요. 실패하지는 않지만, 안 맞는 값을 아무 말 없이 잘라 버려요. 값이 작다는 걸 확실히 알 때만 쓰세요.
fn average(total: i64, count: i64) -> i64 { total / count}
fn main() { let scores: Vec<i32> = vec![80, 95, 70]; let total: i32 = scores.iter().sum();
// i32 -> i64는 항상 들어가니까 .into()면 충분해요 let total: i64 = total.into();
// usize -> i64는 안 들어갈 수도 있으니 try_from으로 먼저 물어봐요 let count = i64::try_from(scores.len()).unwrap();
println!("평균: {}", average(total, count));
// `as`도 되지만, 안 맞는 값은 아무 말 없이 잘라 버려요 let players = scores.len() as i32; println!("플레이어 {players}명");}실행 결과:
평균: 81플레이어 3명타입 명시를 바꾸는 게 가장 간단한 해결책일 때도 많아요. let count: usize = names.len();처럼요. 타입 변환과 변환 페이지에서 더 깊이 다뤄요.
방법 4: Some으로 감싸거나 Option에서 꺼내기
섹션 제목: “방법 4: Some으로 감싸거나 Option에서 꺼내기”error[E0308]: mismatched types --> src/main.rs:3:27 |3 | let position: usize = names.iter().position(|n| *n == "Ben"); | ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `Option<usize>` | | | expected due to thisOption<usize>는 usize가 아니에요. “usize일 수도 있고, 아무것도 없을 수도 있다”는 뜻이에요. 이 불일치는 양쪽 방향으로 일어나요.
- expected
T, foundOption<T>: “없음” 경우를 처리해야 해요.match,if let,unwrap_or(기본값)을 쓰세요. - expected
Option<T>, foundT: 함수가Option을 반환하니까 값을 감싸야 해요.10대신Some(10)이라고 쓰세요.
fn find_discount(code: &str) -> Option<u32> { if code == "SAVE10" { return Some(10); // T -> Option<T>: Some으로 감싸요 } None}
fn main() { let names = vec!["Ana", "Ben", "Cy"];
// Option<T> -> T: "찾지 못함" 경우를 처리하거나... match names.iter().position(|n| *n == "Ben") { Some(i) => println!("Ben은 {i}번 위치에 있어요"), None => println!("Ben은 여기 없어요"), }
// ...unwrap_or로 기본값을 정해요 let discount: u32 = find_discount("HELLO").unwrap_or(0); println!("할인: {discount}%");}실행 결과:
Ben은 1번 위치에 있어요할인: 0%컴파일러가 .expect("REASON")을 제안할 수도 있어요. 그것도 되지만, 값이 None이면 프로그램이 죽어요. 그러니 None이 정말로 나올 수 없을 때만 쓰세요. Option을 참고하세요.
방법 5: if/else와 match의 모든 갈래를 같은 타입으로 맞추기
섹션 제목: “방법 5: if/else와 match의 모든 갈래를 같은 타입으로 맞추기”error[E0308]: `if` and `else` have incompatible types --> src/main.rs:3:52 |3 | let label = if stock > 0 { "in stock" } else { stock }; | ---------- ^^^^^ expected `&str`, found integer | | | expected because of this“if and else have incompatible types”는 “if와 else의 타입이 서로 맞지 않는다”는 뜻이에요.
if나 match가 값을 만들어 낼 때, Rust는 그 값의 타입을 하나로 알아야 해요. 그래서 모든 갈래가 같은 타입을 만들어야 해요. 첫 번째 갈래가 기대하는 타입을 정하고, 에러는 그와 다른 갈래를 가리켜요. match일 때는 메시지가 “match arms have incompatible types”라고 나와요. 튀는 갈래를 나머지와 맞춰 주세요.
fn status_text(code: u16) -> String { match code { 200 => "OK".to_string(), 404 => "Not Found".to_string(), _ => format!("알 수 없는 코드 {code}"), // 모든 갈래가 String이에요 }}
fn main() { let stock = 0; let label = if stock > 0 { "재고 있음" } else { "품절" }; // 둘 다 &str println!("{label}");
println!("{}", status_text(404)); println!("{}", status_text(418));}실행 결과:
품절Not Found알 수 없는 코드 418여기서는 한 갈래에 String을 만드는 format!이 필요했기 때문에, 다른 갈래도 String으로 맞췄어요. if/else와 match를 참고하세요.
자주 보게 되는 곳
섹션 제목: “자주 보게 되는 곳”- 사용자 입력을 읽을 때.
let age: u32 = input.trim();은 “expectedu32, found&str“를 내요. 텍스트는 숫자가 아니에요. 해결: 파싱하세요.let age: u32 = input.trim().parse().unwrap();(또는 에러를 처리하세요. Result 참고). Vec<String>에 리터럴을 넣을 때.tags.push("rust")는 “expectedString, found&str“를 내요. 해결:tags.push("rust".to_string())로 쓰거나, 문자열이 전부 리터럴이라면Vec<&str>로 만드세요.- 서로 다른 숫자 타입으로 계산할 때.
total이i64이고n이i32일 때total + n은 E0308을 내요(옆에 E0277이 같이 나올 때도 많아요). 해결: 한쪽을 먼저 변환하세요.total + i64::from(n).
관련 에러
섹션 제목: “관련 에러”- E0277: trait bound not satisfied (트레이트 바운드 불충족): E0308과 함께 자주 나와요. 예를 들어
i32에i64를 더할 때요. - E0599: no method found (메서드 없음): 이 타입에 없는 메서드를 호출했어요. 값의 타입이 생각했던 것과 달라서 생길 때가 많아요.
- E0425: cannot find value in this scope (스코프에서 값을 찾을 수 없음): Rust가 전혀 모르는 이름이에요.
- 전체 컴파일러 에러: 에러 페이지 전체 목록이에요.
이해도 점검
섹션 제목: “이해도 점검”이해도 점검
1. 함수가 -> i32라고 되어 있는데, 에러는 "expected i32, found ()"라고 해요. 가장 그럴듯한 원인은 무엇일까요?
2. 함수가 &str을 받는데 let name = String::from("Ana");가 있어요. 어떻게 넘길까요?
3. 아무 말 없이 데이터를 잃는 일이 절대 없는 변환은 무엇일까요?
점수: 0 / 3
더 알아보기
섹션 제목: “더 알아보기”- E0308 — Rust error codes index (영어): 이 에러의 공식 설명이에요.
- 반환 값을 갖는 함수 — The Rust Programming Language 한국어판: 끝의 세미콜론 하나가 함수의 반환값을 왜 바꾸는지 설명해요.
- 문자열에 UTF-8 텍스트 저장하기 — The Rust Programming Language 한국어판:
String과&str이 어떤 관계인지, 둘 사이를 어떻게 변환하는지 알려 줘요. - Conversion — Rust by Example (영어):
From,Into,TryFrom,TryInto를 짧은 예제로 보여 줘요. - Option — Rust 표준 라이브러리 (영어):
unwrap_or,map처럼Option에서 값을 꺼내는 모든 메서드가 있어요.