Rustlings Arena
← 아레나로 돌아가기

자주 발생하는 Rust 컴파일러 오류

가장 흔한 Rust 오류를 예제와 수정 방법과 함께 설명합니다.

E0382

이동된 값 사용

error[E0382]: use of moved value: `s1`

힙 할당 값(String이나 Vec 같은)을 다른 변수에 할당하면 소유권이 새 변수로 이동합니다. 원본은 사라집니다 — 이후에 사용할 수 없습니다. 이것이 컴파일 타임에 use-after-free 버그를 제거하는 Rust의 소유권 시스템입니다.

❌ Broken
let s1 = String::from("hello");
let s2 = s1;          // s1 moves into s2
println!("{}", s1);   // ❌ E0382: s1 was moved
✅ Fixed
let s1 = String::from("hello");
let s2 = s1.clone();  // deep copy — both stay valid
println!("{} {}", s1, s2); // ✅

// Or borrow instead of moving:
let s2 = &s1;
println!("{} {}", s1, s2); // ✅
💡 Tip: 기본 타입(i32, bool, char, f64)은 Copy를 구현하며 자동으로 복제됩니다. 힙 타입(String, Vec, Box)은 이동합니다.
E0502

불변으로도 빌림 중인 값을 가변으로 빌릴 수 없음

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable

Rust는 많은 공유(&) 참조 또는 하나의 독점(&mut) 참조를 허용합니다 — 동시에 둘 다는 절대 안 됩니다. 활성 불변 빌림이 있으면 불변 빌림이 끝날 때까지 가변 빌림을 할 수 없습니다. 이 규칙은 구조적으로 데이터 레이스를 제거합니다.

❌ Broken
let mut v = vec![1, 2, 3];
let first = &v[0];    // immutable borrow
v.push(4);            // ❌ E0502: mutable borrow while immutable exists
println!("{}", first);
✅ Fixed
let mut v = vec![1, 2, 3];
let first = v[0];     // copy the value out (i32 is Copy)
v.push(4);            // ✅ no active borrow
println!("{}", first);

// Or restructure scope:
let mut v = vec![1, 2, 3];
{
    let first = &v[0];
    println!("{}", first);
}   // first's borrow ends here
v.push(4);            // ✅
💡 Tip: 빌림 검사기는 보수적입니다. 싸우고 있다면, println을 더 일찍 옮기거나, 빌리는 대신 값을 복사하거나, .clone()을 사용해보세요.
E0499

값을 두 번 이상 가변으로 빌릴 수 없음

error[E0499]: cannot borrow `v` as mutable more than once at a time

한 번에 하나의 가변 참조(&mut)만 존재할 수 있습니다. 이것은 단일 스레드 코드에서 데이터 레이스를 방지하며 Rust의 동시성 보장을 가능하게 하는 기본 규칙입니다.

❌ Broken
let mut v = vec![1, 2, 3];
let a = &mut v;
let b = &mut v;   // ❌ E0499: second mutable borrow
a.push(4);
b.push(5);
✅ Fixed
let mut v = vec![1, 2, 3];
{
    let a = &mut v;
    a.push(4);
}   // a's borrow ends here
let b = &mut v;   // ✅ now safe
b.push(5);
💡 Tip: 범위 {}를 사용하여 가변 빌림이 사는 기간을 제한하세요. 실제로 이 오류는 종종 변경이 순차적으로 일어나도록 로직을 재구성해야 함을 의미합니다.
E0308

타입 불일치

error[E0308]: mismatched types expected `i32`, found `&i32`

컴파일러가 추론하거나 기대하는 타입이 제공한 것과 일치하지 않습니다. 이터레이션 시(이터레이터는 참조를 생성), 함수가 값 대신 ()를 반환할 때, 또는 부호 있는/없는 정수를 혼용할 때 매우 자주 발생합니다.

❌ Broken
// Trailing semicolon returns () instead of i32:
fn double(n: i32) -> i32 {
    n * 2;   // ❌ E0308: returns () not i32
}

// Iterator reference confusion:
let nums = vec![1, 2, 3];
let sum: i32 = nums.iter().sum(); // needs type annotation
✅ Fixed
fn double(n: i32) -> i32 {
    n * 2    // ✅ no semicolon — expression returned
}

// Dereference in closures when iterating:
let nums = vec![1, 2, 3];
let doubled: Vec<i32> = nums.iter().map(|&x| x * 2).collect();
//                                       ^ dereference the &i32
💡 Tip: 함수의 마지막 줄에 붙는 세미콜론은 반환 타입을 ()로 조용히 변경합니다. 이것은 Rust에서 1위 초보자 실수입니다.
E0277

트레이트 바운드 불충족

error[E0277]: `MyType` doesn't implement `std::fmt::Display`

타입이 필요한 트레이트를 구현하지 않습니다. Display 없이 {} 포맷을 사용하거나, PartialOrd 없이 타입을 비교하려 하거나, 특정 트레이트가 필요한 메서드를 호출할 때 가장 자주 발생합니다.

❌ Broken
struct Point { x: i32, y: i32 }

let p = Point { x: 1, y: 2 };
println!("{}", p);  // ❌ E0277: Point doesn't implement Display
println!("{:?}", p); // also fails — needs Debug
✅ Fixed
#[derive(Debug)]
struct Point { x: i32, y: i32 }

let p = Point { x: 1, y: 2 };
println!("{:?}", p);  // ✅ Debug via #[derive(Debug)]

// Implement Display manually for custom formatting:
use std::fmt;
impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}
println!("{}", p);  // ✅
💡 Tip: {:?} 출력에는 #[derive(Debug)]를 추가하세요. {} 출력에는 std::fmt::Display를 직접 구현하세요. 빠진 바운드 대부분은 Debug, Display, Clone, PartialEq, 또는 PartialOrd입니다.
E0106

라이프타임 지정자 누락

error[E0106]: missing lifetime specifier expected named lifetime parameter

함수가 참조를 반환하고 여러 참조 매개변수가 있을 때, Rust는 출력 참조가 어떤 입력에서 오는지 추론할 수 없습니다. 라이프타임 어노테이션으로 알려줘야 합니다.

❌ Broken
// Compiler doesn't know if the return borrows from x or y:
fn longest(x: &str, y: &str) -> &str {   // ❌ E0106
    if x.len() > y.len() { x } else { y }
}
✅ Fixed
// 'a says: output lives no longer than the shorter input
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {  // ✅
    if x.len() > y.len() { x } else { y }
}

// Single input → no annotation needed (elision rule):
fn first_word(s: &str) -> &str {  // ✅ lifetime elided
    let bytes = s.as_bytes();
    for (i, &b) in bytes.iter().enumerate() {
        if b == b' ' { return &s[..i]; }
    }
    &s[..]
}
💡 Tip: 라이프타임 어노테이션은 기간이 아닌 제약입니다. 'a는 '출력이 입력보다 오래 살 수 없다'를 의미합니다. 대부분의 라이프타임은 추론됩니다(생략). 컴파일러가 관계를 알아낼 수 없을 때만 명시적인 것이 필요합니다.
E0596

가변으로 선언되지 않은 값을 가변으로 빌릴 수 없음

error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable

mut으로 선언되지 않은 변수를 수정하려 했습니다. Rust에서 변수는 기본적으로 불변입니다 — 컴파일러가 이를 적용하므로 실수로 인한 변경이 컴파일 타임에 발견됩니다.

❌ Broken
let x = 5;
x = 10;        // ❌ E0596: x is immutable

let v = vec![1, 2, 3];
v.push(4);     // ❌ E0596: v is immutable
✅ Fixed
let mut x = 5;   // ✅ mut makes it mutable
x = 10;

let mut v = vec![1, 2, 3];
v.push(4);     // ✅
💡 Tip: let 뒤에 mut을 추가하세요. 변수가 실제로 변경되어야 할 때만 mut을 추가하세요 — 기본 불변성은 버그를 잡고 코드를 이해하기 쉽게 만듭니다.
E0384

불변 변수에 두 번 할당할 수 없음

error[E0384]: cannot assign twice to immutable variable `x`

E0596과 유사하지만 메서드 호출이 아닌 두 번째 할당으로 인해 발생합니다. 변수가 mut 없이 선언되었지만 재할당하려 했습니다.

❌ Broken
let x = 5;
x = 10;  // ❌ E0384
✅ Fixed
let mut x = 5;
x = 10;  // ✅

// Shadowing is another option if you want to "replace" the variable:
let x = 5;
let x = 10;  // ✅ shadowing — creates a new binding named x
💡 Tip: 섀도잉(let x = ...)과 변경(mut x = ...)은 비슷해 보이지만 다릅니다: 섀도잉은 새 변수를 만들고(타입도 변경 가능), 변경은 같은 바인딩을 수정합니다.
E0507

빌린 내용에서 이동할 수 없음

error[E0507]: cannot move out of `*s` which is behind a shared reference

참조를 통해 값을 이동하려 했습니다. &T를 통해 소유권을 가져올 수 없습니다 — 참조는 데이터를 소유하지 않습니다.

❌ Broken
fn print_name(s: &String) {
    let owned = *s;  // ❌ E0507: can't move out of &String
    println!("{}", owned);
}
✅ Fixed
fn print_name(s: &String) {
    let owned = s.clone();  // ✅ clone the data
    println!("{}", owned);
}

// Or just use the reference directly:
fn print_name(s: &String) {
    println!("{}", s);  // ✅ no need to own it
}
💡 Tip: 값을 읽기만 필요하면 참조를 직접 사용하세요. 소유된 복사본이 필요하면 .clone()을 호출하세요. 소유권이 필요하면 매개변수를 &T 대신 T를 받도록 변경하세요.
E0004

match에서 비완전 패턴

error[E0004]: non-exhaustive patterns: `None` not covered

match 표현식은 모든 가능한 경우를 처리해야 합니다. 처리되지 않은 변형이 있으면 코드가 컴파일되지 않습니다. 이것은 처리되지 않은 경우가 크래시를 유발하는 런타임 버그의 전체 클래스를 제거합니다.

❌ Broken
let opt: Option<i32> = Some(42);
match opt {
    Some(n) => println!("{}", n),
    // ❌ E0004: None not covered
}
✅ Fixed
match opt {
    Some(n) => println!("{}", n),
    None    => println!("nothing"),  // ✅ exhaustive
}

// Use _ as a catch-all:
match opt {
    Some(n) => println!("{}", n),
    _       => {},  // ✅ handles everything else
}

// Or use if let for a single case:
if let Some(n) = opt {
    println!("{}", n);  // ✅ no else needed
}
💡 Tip: 컴파일러는 어떤 변형이 빠졌는지 정확히 알려줍니다. _는 포괄 처리입니다. if let은 하나의 패턴만 신경 쓸 때의 단축 표현입니다.
E0072

재귀 타입의 무한 크기

error[E0072]: recursive type `List` has infinite size

각 변형이 직접 자신을 포함하는 재귀 타입은 할당에 무한한 메모리가 필요합니다. Rust는 컴파일 타임에 모든 타입의 크기를 알아야 합니다. 해결책은 알려진 포인터 크기를 가진 Box를 통한 간접 참조입니다.

❌ Broken
enum List {
    Cons(i32, List),  // ❌ E0072: infinite size
    Nil,
}
✅ Fixed
enum List {
    Cons(i32, Box<List>),  // ✅ Box has fixed pointer size (8 bytes)
    Nil,
}

let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
💡 Tip: Box<T>는 가장 단순한 힙 포인터입니다. T에 관계없이 고정 크기(포인터 하나 너비)를 가져 재귀 크기 계산을 끊습니다.
E0716

빌리는 동안 임시 값이 삭제됨

error[E0716]: temporary value dropped while borrowed

임시 값을 만들고 참조를 가져갔는데 참조가 사용되기 전에 임시 값이 삭제되었습니다. 참조가 해제된 메모리를 가리키는 댕글링 참조가 될 것입니다.

❌ Broken
let s: &str = &String::from("hello")
    .to_uppercase();  // ❌ E0716: temporary dropped

// Also common with chains:
let name = get_user().name;  // if get_user() returns a temp
✅ Fixed
// Bind the temporary to a variable first:
let upper = String::from("hello").to_uppercase();
let s: &str = &upper;  // ✅ upper lives long enough
println!("{}", s);
💡 Tip: 이 오류가 발생하면 중간 값을 보유할 변수를 도입하세요. let 바인딩의 라이프타임은 블록 끝까지 연장됩니다.
E0515

지역 변수를 참조하는 값을 반환할 수 없음

error[E0515]: cannot return value referencing local variable `local`

지역 변수에 대한 참조를 반환하려 했습니다. 함수가 반환하면 지역 변수가 삭제되어 댕글링 참조가 됩니다. Rust는 컴파일 타임에 이를 방지합니다.

❌ Broken
fn get_greeting() -> &str {
    let s = String::from("hello");
    &s   // ❌ E0515: s is dropped when function returns
}
✅ Fixed
// Return an owned value:
fn get_greeting() -> String {
    String::from("hello")  // ✅ caller owns it
}

// Or return a &'static str literal:
fn get_greeting() -> &'static str {
    "hello"  // ✅ lives for the entire program
}
💡 Tip: 참조를 반환해야 한다면 함수의 입력(매개변수에서 빌림)에서 오거나 'static이어야 합니다. 확실하지 않으면 소유된 타입(String, Vec 등)을 반환하세요.
E0369

이진 연산을 적용할 수 없음

error[E0369]: binary operation `>` cannot be applied to type `T`

제네릭 타입 T에 연산자를 사용했지만 T에 해당 연산자가 존재해야 한다는 트레이트 바운드가 없습니다. 제네릭 코드는 타입이 지원하는 연산에 대해 명시적이어야 합니다.

❌ Broken
fn largest<T>(list: &[T]) -> &T {
    let mut biggest = &list[0];
    for item in list {
        if item > biggest {  // ❌ E0369: T doesn't implement PartialOrd
            biggest = item;
        }
    }
    biggest
}
✅ Fixed
fn largest<T: PartialOrd>(list: &[T]) -> &T {  // ✅ add bound
    let mut biggest = &list[0];
    for item in list {
        if item > biggest {
            biggest = item;
        }
    }
    biggest
}
💡 Tip: E0369가 발생하면 오류 메시지가 필요한 트레이트를 알려줍니다: 비교에는 PartialOrd, +에는 Add, {}에는 Display 등. 바운드로 추가하세요: <T: TraitName>.
E0428

이름이 여러 번 정의됨

error[E0428]: the name `connect` is defined multiple times

같은 범위에서 같은 이름을 가진 두 항목(함수, 구조체, 타입 등)을 정의했습니다. Rust는 이를 허용하지 않습니다 — 이름은 모듈 내에서 고유해야 합니다.

❌ Broken
fn connect() -> String { "tcp".into() }
fn connect() -> String { "udp".into() }  // ❌ E0428: duplicate
✅ Fixed
fn connect_tcp() -> String { "tcp".into() }
fn connect_udp() -> String { "udp".into() }  // ✅ unique names

// Or use a parameter:
fn connect(protocol: &str) -> String { protocol.into() }
💡 Tip: 충돌하는 이름을 가져오는 use 문에서도 발생합니다. as로 이름을 변경하세요: use std::io::Error as IoError;
E0061

인수 개수 오류

error[E0061]: this function takes 2 arguments but 3 arguments were supplied

함수의 시그니처가 선언한 것보다 많거나 적은 인수로 함수를 호출했습니다. Rust는 기본 매개변수나 가변 인수 함수를 지원하지 않습니다(println! 같은 매크로를 통한 경우 제외).

❌ Broken
fn add(a: i32, b: i32) -> i32 { a + b }

add(1, 2, 3);  // ❌ E0061: 3 args, expected 2
add(1);        // ❌ E0061: 1 arg, expected 2
✅ Fixed
add(1, 2);  // ✅

// For optional parameters, use Option:
fn greet(name: &str, title: Option<&str>) {
    match title {
        Some(t) => println!("{} {}", t, name),
        None    => println!("{}", name),
    }
}
greet("Alice", None);
greet("Bob", Some("Dr."));
💡 Tip: Rust에는 기본 매개변수가 없습니다. 선택적 값에는 Option<T>를, 많은 선택적 필드가 있는 함수에는 빌더 구조체를 사용하세요.
E0433

해결 실패 — 범위에서 찾을 수 없음

error[E0433]: failed to resolve: use of undeclared crate or module `HashMap`

범위에 없는 타입이나 함수를 사용했습니다. 프렐루드 외부의 표준 라이브러리 타입(HashMap, BTreeMap, BufReader 같은)에는 명시적 use 문이 필요합니다.

❌ Broken
let mut map = HashMap::new();  // ❌ E0433: HashMap not in scope
✅ Fixed
use std::collections::HashMap;

let mut map = HashMap::new();  // ✅

// Common imports:
use std::collections::{HashMap, HashSet, BTreeMap};
use std::io::{self, BufRead, Write};
use std::fmt;
💡 Tip: Rust 프렐루드는 일반 타입(Vec, String, Option, Result 등)을 자동으로 가져옵니다. 그 외 모든 것은 명시적 use가 필요합니다. Rust Analyzer / rust-analyzer가 올바른 use 경로를 제안합니다.

세미콜론 예상 / 표현식 예상

error: expected `;`, found `let` error: expected expression, found keyword `fn`

구문 오류 — 파서가 예상치 못한 것을 발견했습니다. 보통 세미콜론 누락, 닫히지 않은 구분자, 키워드의 오타, 또는 잘못된 위치의 표현식입니다.

❌ Broken
fn main() {
    let x = 5     // ❌ missing semicolon
    let y = 10;

    fn inner() {} // ❌ nested fn needs to be at statement level
    x + y         // if this is a statement, not a return, add ;
}
✅ Fixed
fn main() {
    let x = 5;     // ✅ semicolon added
    let y = 10;

    fn helper() {}  // ✅ nested fn is valid in Rust
    println!("{}", x + y);
}
💡 Tip: Rust의 오류 메시지는 거의 항상 올바른 줄을 가리킵니다. 세미콜론 오류가 이상해 보이면 위 줄을 확인하세요 — 이전 문에 ;가 누락되면 파서가 이상한 상태가 됩니다.

요구 사항 평가 시 오버플로우

error[E0275]: overflow evaluating the requirement `Box<T>: Sized`

컴파일러가 트레이트 바운드를 증명하려는 무한 루프에 빠졌습니다. 재귀 트레이트 구현이나 자신을 필요로 하는 트레이트를 구현하려는 타입에 의해 가장 흔히 발생합니다.

❌ Broken
// Generic function calling itself with an incompatible bound:
fn process<T: Clone>(x: T) {
    process(x.clone()); // infinite recursion in type inference
}
✅ Fixed
// Add a base case or restructure logic:
fn process(x: String) {
    if x.is_empty() { return; }
    process(x[1..].to_string());
}
💡 Tip: 이 오류는 거의 항상 재귀 타입이나 트레이트 바운드 루프를 가리킵니다. 트레이트 제약을 단순화하거나 명시적 타입 매개변수를 추가하세요.

이 범위에서 값을 찾을 수 없음

error[E0425]: cannot find value `x` in this scope

현재 범위에 존재하지 않는 변수를 참조했습니다. 일반적인 원인: 변수 이름 오타, 끝난 내부 블록에서 선언된 변수, 또는 루프 외부에서 사용된 루프 변수.

❌ Broken
{
    let x = 5;
}
println!("{}", x);  // ❌ x is out of scope

for i in 0..10 { }
println!("{}", i);  // ❌ i only lives inside the for loop
✅ Fixed
let x = 5;         // declare outside the block
{
    println!("{}", x); // ✅ x is in scope
}
println!("{}", x); // ✅ still in scope

// Save the last loop value explicitly:
let mut last = 0;
for i in 0..10 { last = i; }
println!("{}", last);  // ✅
💡 Tip: Rust 범위는 블록 기반입니다. 변수는 선언된 곳부터 감싸는 블록의 끝까지만 존재합니다. 더 넓은 범위가 필요하면 선언을 위로 옮기세요.
🦀

시작할 준비가 됐나요?

26개의 무료 인터랙티브 챌린지. 설치 불필요. 계정 없이 바로 시작.

Rust 배우기 시작 →