Rust는 안전하고 신뢰할 수 있는 소프트웨어를 작성하기 위해 강력한 에러 처리 메커니즘을 제공합니다. Rust의 에러 처리는 크게 두 가지로 나뉩니다: 패닉(panic)과 Result 타입을 이용한 에러 처리입니다.
패닉은 프로그램이 복구할 수 없는 에러가 발생했을 때 사용됩니다. 패닉이 발생하면 프로그램은 즉시 중단되고 스택 트레이스를 출력합니다. 패닉은 주로 심각한 버그나 논리적 오류가 발생했을 때 사용됩니다.
fn main() {
let v = vec![1, 2, 3];
// 존재하지 않는 인덱스에 접근하여 패닉 발생
v[99];
}
위 예제에서 벡터의 존재하지 않는 인덱스에 접근하면 패닉이 발생합니다.
Rust의 Result 타입은 복구 가능한 에러를 처리하는 데 사용됩니다. Result 타입은 두 가지 변형을 가집니다: Ok(T)와 Err(E). Ok(T)는 성공적인 결과를 나타내고, Err(E)는 에러를 나타냅니다.
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("hello.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
fn main() {
match read_username_from_file() {
Ok(username) => println!("Username: {}", username),
Err(e) => println!("Error reading file: {}", e),
}
}
위 예제에서 File::open과 file.read_to_string 함수는 Result 타입을 반환합니다. ? 연산자는 에러가 발생하면 해당 에러를 호출자에게 반환하고, 그렇지 않으면 값을 언패킹합니다.
에러 전파는 함수가 에러를 처리하지 않고 호출자에게 전달하는 것을 의미합니다. ? 연산자를 사용하여 에러를 쉽게 전파할 수 있습니다.
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut file = File::open("hello.txt")?;
let mut username = String::new();
file.read_to_string(&mut username)?;
Ok(username)
}
위 예제에서 ? 연산자는 에러가 발생하면 해당 에러를 호출자에게 반환합니다.
Rust에서는 커스텀 에러 타입을 정의하여 더 구체적인 에러 처리를 할 수 있습니다.
use std::fmt;
#[derive(Debug)]
enum CustomError {
NotFound,
PermissionDenied,
}
impl fmt::Display for CustomError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
CustomError::NotFound => write!(f, "Not Found"),
CustomError::PermissionDenied => write!(f, "Permission Denied"),
}
}
}
fn get_error(is_error: bool) -> Result<(), CustomError> {
if is_error {
Err(CustomError::NotFound)
} else {
Ok(())
}
}
fn main() {
match get_error(true) {
Ok(_) => println!("No error"),
Err(e) => println!("Error: {}", e),
}
}
위 예제에서 CustomError 열거형을 정의하고, fmt::Display 트레이트를 구현하여 에러 메시지를 출력합니다.
panic! 매크로
panic!("문제가 발생했습니다!"); // 프로그램이 중단되고 에러 메시지가 출력됩니다.Result<T, E>
Result 열거형
let result: Result<i32, &str> = Ok(10); // 성공적인 결과를 나타냅니다.match로 Result 처리
match result {
Ok(value) => println!("값: {}", value),
Err(e) => println!("에러: {}", e),
}연산자 ?
예제:
fn divide(a: i32, b: i32) -> Result<i32, String> {
if b == 0 {
return Err("0으로 나눌 수 없습니다.".to_string());
}
Ok(a / b)
}
let result = divide(10, 0)?;
println!("결과: {}", result);
에러 전파
fn read_file() -> Result<String, std::io::Error> {
let content = std::fs::read_to_string("파일.txt")?;
Ok(content)
}사용자 정의 에러 타입
예제:
#[derive(Debug)]
enum MyError {
NotFound,
PermissionDenied,
}
fn do_something() -> Result<(), MyError> {
Err(MyError::NotFound)
}
Rust의 에러 처리 메커니즘을 사용하면 안전하고 신뢰할 수 있는 소프트웨어를 작성할 수 있습니다. 패닉은 복구할 수 없는 에러에 사용하고, Result 타입은 복구 가능한 에러에 사용하여 프로그램의 안정성을 높일 수 있습니다.