러스트의 에러 범주
- 복구 가능한 (recorverable) 에러
사용자에게 문제를 보고하고 명령을 재시도 하는 에러
러스트에는 예외처리라는 것이 없기 때문에 Result<T, E> 와 같이 복구 가능한 에러 타입을 제공
- 복구 불가능한 (unrecorverable) 에러
언제나 버그 증상이 나타나는 에러로 프로그램을 즉시 멈춰야하는 에러
프로그램을 종료하는 panic! 매크로
panic!으로 복구 불가능한 에러 처리하기
패닉을 일으키기 위해서는 코드가 패닉을 일으킬 동작을 하거나 panic! 매크로를 명시적으로 호출해야함
기본적으로 이러한 패닉은 실패 메시지를 출력하고 되감고(unwind), 스택을 청소하고 종료함
패닉이 발생 했을 때 그 패닉의 근원을 쉽게 추적하기 위해 환경 변수를 통하여 러스트가 호출 스택을 보여주도록 할 수 있음
- 되감기 (unwinding)
러스트가 패닉을 발생시킨 각 함수로 부터 스택을 거꾸로 훑어가면서 데이터를 청소하는 것
러스트에서는 패닉 발생 시 기본적으로 되감기를 수행하나, 프로젝트 내에서 결과 바이너리를 가능한 작게 만들기 위해 패닉 발생 시 데이터 정리 작업 없이 즉각 종료되는 그만두기(abort)를 수행하도록 설정할 수 있음
// Cargo.toml
[profile.release]
panic = 'abort'
패닉 발생 시키기
fn main() {
panic!('crash and burn');
}
위 코드를 실행하면 패닉 메시지와 패닉이 발생한 소스 코드 지점을 보여줌
문제를 일으킨 코드 조각을 발견하기 위해서 panic! 호출이 발생한 함수에 대한 백트레이스(backtrace)를 사용할 수 있음
- 백트레이스 (backtrace)
어떤 지점에 도달하기까지 호출한 모든 함수의 목록을 의미. 러스트의 백트레이스는 다른 언어들과 마찬가지로 작동.
백트레이스는 위에서 부터 시작하여 아래(문제를 일으킨 실제 지점)로 적혀있음
러스트의 백트레이스를 활성화하기 위해서는 `RUST_BACKTRACE` 환경변수를 0이 아닌 값으로 설정해야함
그리고 디버그 심벌이 활성화 되어있어야함
디버그 심벌은 cargo build 나 cargo run을 --release 플래그 없이 실행했을 때 기본적으로 활성화됨
Result로 복구 가능한 에러 처리하기
// T: 성공한 경우에 OK 배리언트 안에 반환될 값
// E: 실패한 경우에 Err 배리언트 안에 반환될 값
enum Result<T, E> {
OK(T),
Err(E),
}
use std::fs::File;
fn main() {
let open_file_result = File::open("hello.txt");
// Result 열거형과 배리언트들은 프렐루드 (자동으로 가져오는 표준 라이브러리의 기본 요소 집합. 타입, 트레이트, 매크로)로서 match 갈래의 Ok와 Err 앞에는 Redult::를 지정하지 않아도 됨
let open_file = match file_result {
OK(file) => file,
// 디렉터리 내에 hello.txt 파일이 없으면 panic! 매크로로 부터 출력을 받음
Err(error) => panic!("problem opening file: {::?}". error),
};
}
서로 다른 에러에 대해 매칭하기
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let open_file_result = File::open("hello.txt");
let open_file = match open_file_result {
OK(file) => file,
// File::open의 Err 배리언트 값의 타입이 io::Error이므로 kind 메서드로 io::ErrorKind 값을 얻을 수 있음
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("failed creating file {:?}", e),
},
other_error => {
panic!("other error {::?}", e);
}
},
};
}
Result<T,E>와 match 사용에 대한 대안
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let open_file_result = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("failed to create file {:?}", error);
})
} else {
panic!("failed open file {:?}", error);
}
});
}
match를 사용할 때와 동일하게 작동하는 대신 더 깔끔하게 읽을 수 있음
- unwrap()
match 구문과 비슷한 구현을 한 숏컷 메서드
Result 값이 Ok 배리언트면 Ok 내의 값을 반환하고 Err이면 panic! 매크로를 호출
use std::fs::File;
fn main() {
let open_file = File::open("hello.txt").unwrap();
}
- expect()
unwrap과 비슷한 숏컷 메서드. panic! 에러 메시지를 선택할 수 있음
use std::fs::File;
fn main() {
let open_file = File::open("hello.txt")
.expect("Faile to open file");
}
에러 전파하기 (error propagation)
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt");
let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e), // 함수 자체를 일찍 끝내고 File::open()의 오류(io::Error)를 반환
};
// username_file에 파일 핸들을 얻게 되면
// read_to_string 메서드를 호출하여 파일의 내용을 username에 할당
let mut username = String::new();
// 함수의 마지막 표현식이기 때문에 return 키워드가 필요없음
match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username), // String을 담은 Ok 값 반환
Err(e) => Err(e), // read_to_string() 오류(io::Error)를 반환
}
}
이 함수를 호출하는 코드는 username이 있는 Ok 값 혹은 io::Error을 담은 Err 값을 처리하게 됨
이 값을 가지고 어떤 일을 할지에 대해서는 호출하는 코드 쪽에 달려있음 (panic!을 호출하거나 다른 행위를 수행 가능)
- 에러 전파를 위한 숏컷 ? 연산자
? 연산자를 사용하면 보일러 플레이트를 제거해주고 함수의 구현을 단순하게 할 수 있음
아래 코드는 위 error propagation 예제와 동일하게 작동
?은 ?이 사용된 값과 호환 가능한 반환 타입을 가진 함수에서만 사용할 수 있음
즉, 함수의 반환 타입이 Result, Option, FromResidual인 경우에만 사용할 수 있음
단, Result를 반환하는 함수에서는 Result에서 ? 연산자를 사용할 수 있고
Option을 반환하는 함수에서는 Option에 대해 ? 연산자를 사용할 수 있음
혼용해서 사용은 불가
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username = Strimg::new();
// ?은 에러 발생 시 함수로 부터 일찍 빠져나와 호출하는 코드에게 Err 값을 전달
// 정상인 경우에는 Ok를 username에 반환
File::open("heelo.txt")?.read_to_string(&mut username)?;
Ok(username)
}
fn last_char_of_first_line(text: &str) -> Option<char> {
// Option<T> 값에 대한 ? 연산자 사용
// None 값이 나오면 해당 지점에서 None 값을 일찍 반환
// Some 값인 경우 some 안에 있는 값이 표현식의 결과값이 됨
text.lines().next()?.chars().last()
}
- 파일을 열고 읽기 대신 fs::read_to_string 사용
표준 라이브러리에서는 파일을 열고, 새 String을 생성하고, 파일 내용을 읽고, 내용을 String에 집어넣고 반환하는 편리한 함수를 제공
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
panic!을 사용하는 시기
코드가 패닉을 일으킬 때는 복구할 방법이 없음
복구 가능한 상황이어도 명시적으로 panic! 을 호출하면 복구 불가능한 상황이 됨
고로 Result를 반환 하도록 하면 호출하는 족에 옵션을 제공할 수 있음
그러므로 실패할지도 모르는 함수를 정의할 때는 기본적으로 Result를 반환하는 것이 좋은 선택
상황에 따라 unwrap이나 expect 메서드로 논리적으로는 Err 배리언트가 나오지 않음을 표시
'Programming > Rust' 카테고리의 다른 글
| 러스트 자동화 테스트 (0) | 2025.09.11 |
|---|---|
| 러스트 - 제네릭 타입, 트레이트, 라이프타임 (1) | 2025.09.08 |
| 러스트 컬렉션 (collection) (1) | 2025.09.02 |
| 러스트 프로젝트 관리 (패키지, 크레이트, 모듈) (4) | 2025.09.01 |
| 러스트의 열거형과 패턴 매칭 (1) | 2025.08.24 |
