본문 바로가기

Rust

[Rust] 에러 처리

안녕하세요.

오늘은 Rust의 에러 처리에 대해 알아보도록 하겠습니다.


📌 Rust의 에러 처리

Rust는 Exception 대신, 오류를 2가지로 나눠서 에러를 처리합니다.

  1. 복구 가능한 오류 (Recoverable Errors) → Result<T, E> 사용
  2. 복구 불가능한 오류 (Unrecoverable Errors) → panic! 사용

📌 복구 가능한 오류 (Recoverable Errors)

✅ 정의

  • 프로그램이 정상적으로 실행을 이어나갈 수 있는 오류 입니다
  • 적절한 오류 처리 후 다시 실행을 이어나갈 수 있습니다

 

✅ 해결 방법

  • Result<T, E>를 사용합니다
  • 호출자가 에러를 직접 처리할 수 있도록 반환합니다

 

예제) 파일 읽을 때 오류 발생

파일이 존재하지 않으면, 새 파일을 생성하는 방식으로 복구 가능한 예제입니다

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let file = File::open("hello.txt");

    let file = match file {
        Ok(file) => file, // ✅ 성공하면 파일을 반환
        Err(error) => match error.kind() {
            ErrorKind::NotFound => {
                println!("파일이 없습니다. 새 파일을 생성합니다.");
                File::create("hello.txt").unwrap() // ❗ 복구: 새 파일 생성
            }
            other_error => panic!("파일을 열 수 없습니다: {:?}", other_error),
        },
    };
}

📌 복구 불가능한 에러 (Unrecoverable Errors)

✅ 정의

  • 프로그램이 정상적으로 실행될 수 없는 오류를 의미합니다
  • 잘못된 상태에서 계속 실행되는 것을 방지하기 위해 있는 에러입니다

 

✅ 해결 방법

  • panic!을 사용하여 프로그램을 즉시 종료합니다

 

예제) 배열 인덱스 초과

fn main() {
    let numbers = vec![1, 2, 3];
    println!("{}", numbers[10]); // ❌ 존재하지 않는 인덱스 접근 → panic!
}


📌 복구 불가능한 에러 처리 방법

위의 예제에서는 panic!을 일으켜 프로그램이 종료되지만,

Java 언어에서는 IndexOutOfBound Exception을 반환해, try-catch로 감싸서 프로그램이 종료되는 것을 방지합니다

 

이처럼 Rust 에서도 프로그램을 강제 중단시키고 싶지 않은 경우에, 어떻게 처리를 할 수 있을까요?

대표적으로 2가지가 있습니다.

 

✅ solve1) get() 메서드 사용

get() 메서드를 사용해서 index에 접근하면, Option<T>를 반환 받습니다

  • index가 배열 범위 내라면 → Some<T> 반환
  • index가 배열 범위 외라면 → None 반환
fn main() {
    let numbers = vec![1, 2, 3];

    match numbers.get(10) {
        Some(value) => println!("값: {}", value),
        None => println!("잘못된 인덱스 접근!"), // ❌ panic 발생 X
    }
}

 

✅ solve2) catch_unwind 사용

위의 방식은 패닉을 사전에 차단하는 방식이라면,

catch_unwind 방식은 패닉이 일어난 이후 처리하는 사후 처리 방식 입니다.

use std::panic;

fn main() {
    let result = panic::catch_unwind(|| {
        let numbers = vec![1, 2, 3];
        println!("{}", numbers[10]); // ❌ panic 발생
    });

    if result.is_err() {
        println!("패닉이 발생했지만 복구했습니다.");
    }
}
C:/Users/kdr06/.cargo/bin/cargo.exe run --color=always --package practice1 --bin practice1 --profile dev
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.01s
     Running `target\debug\practice1.exe`
thread 'main' panicked at src\main.rs:6:31:
index out of bounds: the len is 3 but the index is 10
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58\library/std\src\panicking.rs:665
   1: core::panicking::panic_fmt
             at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58\library/core\src\panicking.rs:76
   2: core::panicking::panic_bounds_check
             at /rustc/e71f9a9a98b0faf423844bf0ba7438f29dc27d58\library/core\src\panicking.rs:281
   3: core::slice::index::impl$2::index<i32>
             at C:\Users\kdr06\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\slice\index.rs:274
   4: alloc::vec::impl$13::index<i32,usize,alloc::alloc::Global>
             at C:\Users\kdr06\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\alloc\src\vec\mod.rs:3346
   5: practice1::main::closure$0
             at .\src\main.rs:6
   6: std::panicking::try::do_call<practice1::main::closure_env$0,tuple$<> >
             at C:\Users\kdr06\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panicking.rs:557
   7: std::panic::catch_unwind<practice1::main::closure_env$0,tuple$<> >
   8: std::panicking::try
             at C:\Users\kdr06\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panicking.rs:520
   9: std::panic::catch_unwind<practice1::main::closure_env$0,tuple$<> >
             at C:\Users\kdr06\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\std\src\panic.rs:358
  10: practice1::main
             at .\src\main.rs:4
  11: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
             at C:\Users\kdr06\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:250
  12: core::hint::black_box
             at C:\Users\kdr06\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\hint.rs:389
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
패닉이 발생했지만 복구했습니다.

Process finished with exit code 0

 

❌ 이 방식은 권장되는 방식은 아님!

panic!이 발생한 뒤에는 프로그램이 예측할 수 없는 상태가 되어 버립니다.

억지로 복구하는 방법 대신, 사전에 방지하는 방식을 사용하는 것이 좋습니다.


📌 ?연산자

에러 처리를 간결하게 만들기 위해 ?연산자를 사용할 수 있습니다

  • Err를 만나면 즉시 호출자에게 전파합니다

 

예제) 0으로 나누기

0으로 나누는 코드는 Unrecoverable Error로, panic!을 일으킵니다

fn divide(a: i32, b: i32) -> i32 {
    a / b // ❌ b가 0이면 `panic!` 발생
}

fn main() {
    let result = divide(10, 0); // ❌ panic 발생
    println!("결과: {}", result);
}

 

 

이를 Result<T, E>를 사용해 Recoverable Error로 바꿀 수 있습니다

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("0으로 나눌 수 없습니다!".to_string()) // ❌ 에러 반환
    } else {
        Ok(a / b)
    }
}

fn main() {
    let result = divide(10, 0);

    match result {
        Ok(value) => println!("결과: {}", value),
        Err(err) => println!("에러 발생: {}", err), // 🔥 panic 없이 에러 처리 가능
    }
}

 

?연산자를 사용하면 에러를 전파할 수 있습니다

이를 통해 책임 분리가 가능합니다

  • 값 처리는 compute_division()에서 처리
  • 에러는 호출자에게 전파해서, 최종적으로 main()에서 처리
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("0으로 나눌 수 없습니다!".to_string())
    } else {
        Ok(a / b)
    }
}

// `?` 연산자로 에러 전파
fn compute_division() -> Result<(), String> {
    let result = divide(10, 0)?; // ❌ 에러 발생 시 즉시 반환
    println!("결과: {}", result);
    Ok(())
}

fn main() {
    if let Err(err) = compute_division() {
        println!("에러 발생: {}", err); // 🔥 여기서 에러 처리
    }
}