🦀 Rust
[Rust 입문] 8. 패닉
date
Mar 23, 2023
slug
rust-panic
author
status
Public
tags
Rust
독학
summary
rust의 panic에 대한 설명입니다.
type
Post
thumbnail
category
🦀 Rust
updatedAt
Mar 23, 2023 11:24 AM
rust는 try ~ catch 문을 지원하지 않는다. 그래서 에러가 발생할만한 상황에서 수동으로 처리해야 한다.
rust에선 “복구 가능한 에러"와 “복구 불가능한 에러" 가 있다.
panic 매크로
panic 매크로는 복구가 불가능한, 프로그램을 즉시 종료해야 할 상황에서 호출한다. 이 매크로를 호출하면 호출 스택을 역으로 되감으며 데이터를 제거한다.
이런 기능이 다소 무거울 수도 있으므로 제거하길 원한다면 Cargo.toml 파일의 [profile] -> panic = 'abort'를 추가해야 한다.
fn main() { panic!("패닉"); }
panic 매크로에 발생하는 “에러" 상황은 복구가 불가능하다. 배열의 참조할 수 없는 공간을 참조하거나 할 때 발생할 수 있다.
오류가 발생되면 "note: Run with
RUST_BACKTRACE=1 for a backtrace." 라는 메시지가 뜨는데 프로그램을 실행할 때 저 환경변수를 저렇게 쓰면 백트레이스를 출력해준다. 근데 이런 정보들은 함수의 심볼 정보들을 포함하고 있으므로 -release 옵션을 붙여서 릴리즈 하는 바이너리에선 제거한다.Result 열거형 타입
프로그램 실행 중 프로그램을 즉시 종료할 필요할 정도의 에러가 아니라면 Result 열거형을 사용해서 에러를 처리하는 것이 보통이다.
어떤 함수나 메소드를 실행할 때 성공 혹은 실패 여부를 Result 열거형 타입으로 리턴하여 성공할 때, 실패할 때를 상정하여 처리할 수 있도록 한다.
Result 타입의 정의는 다음과 같으며 제네릭을 이용해 성공, 실패 시에 리턴되는 타입을 유동적으로 설정할 수 있다.
enum Result<T, E> { Ok(T), Err(E), }
Option 열거형처럼 match나 if let을 이용해 성공할 때와 실패할 때에 대한 처리를 하게 하거나 Result의 메소드를 이용해 처리를 할 수 있다.
Optional이나 Result 모두 별도로 import하지 않고 사용 가능하며 std::prelude에 정의되어 있다.
다음은 파일을 여는 동작에 대한 성공/실패 처리이다.
fn main() { let file = File::open("hello.txt"); let mut file = match file { Ok(file) => file, Err(error) => { panic!("파일 열기 실패 ({:?})", error); } }; }
Optional에서 처리와 비슷하다. 여기서 에러 상황에 대해 좀 더 상세하게 구분지어야 할 필요가 있다. error 인자의 상태에 대한 것인데 파일을 열 권한이 없거나 파일이 존재하지 않을 때 등 여러 가지 종류의 에러 상황이 있을 것이고 이런 정보는 error 인자에 기재되어 있다.
rust의 match 키워드는 매칭을 할 때 좀 더 상세하게 할 수 있는 방법을 지원하는데 이 때 match guard (https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html#extra-conditionals-with-match-guards) 라는 방법을 사용한다.
fn main() { let file = File::open("hello.txt"); let mut file = match file { Ok(file) => file, Err(ref error) if error.kind() == ErrorKind::NotFound => { panic!("파일이 없습니다."); // 원하면 File::create 함수와 match 키워드를 이용해 파일을 생성해 File을 리턴하게 함 } Err(error) => { panic!("파일 열기 실패 ({:?})", error); } }; }
패턴 매칭 뒤에 if문으로 조건을 검사하는 로직이 추가로 들어간다. 그리고
ref 키워드가 추가로 들어가는데 match 문에서 패턴 매칭을 시도할 때 소유권이 매칭되는 부분으로 이전되기 때문이다. 그래서 소유권을 이전하지 않고 값을 빌려오기 위해 사용하는 것이다.& 가 아니라 ref 라는 키워드를 사용하는 이유는 & 을 사용하면 참조형인 값에 매칭한다는 의미가 되며 ref 를 사용하면 매칭하고자 하는 값을 참조형으로 빌려오겠다 라는 의미가 되기 때문이다. (https://doc.rust-lang.org/std/keyword.ref.html 참조)Result의 메소드 사용
match를 사용하면 장황한 단점이 있다. 그래서 여러 메소드를 제공하여 상황에 맞게 에러를 처리할 수 있도록 한다.
대표적인 에러 처리 메소드엔
unwrap 과 expect 가 있다.unwrap 메소드는 호출 시에 실패하면 바로 panic을 발생시키며 expect 는 에러가 발생할 때 리턴할 panic 메시지를 지정한다.let file = File::open("hello.txt").unwrap(); let file = File::open("hello.txt").expect("err");
에러 전파
에러를 (Result를 통해 전달되는) 호출한 함수의 결과로 전달할 수 있다. 이를 에러 전파하고 부르며 에러가 발생할 때 에러를 리턴한다.
아래의 코드는 인자로 파일명을 전달받아 파일의 내용을 읽어 문자열로 반환하는 함수이다.
use std::io; use std::io::Read; use std::fs::File; fn main() { let r = read_file("./Cargo.toml"); match r { Ok(str) => println!("file : {}", str), Err(e) => panic!("err! (e : {:?}", e) }; } fn read_file(file: &str) -> Result<String, io::Error> { let f = File::open(file); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } }
read_file 함수는 지속적으로 에러가 발생하는 구간에 Err을 리턴하도록 시도하게 되는데 비교적 장황하다는 단점이 있다.
그래서 보통 물음표 연산자
? 을 이용해 에러를 전달한다. 그리고 물음표 연산자는 체이닝도 지원하기 때문에 유용하게 사용할 수 있다.아래 코드는 위 코드와 동일한 동작을 한다.
use std::io; use std::io::Read; use std::fs::File; fn main() { let r = read_file("./Cargo.toml"); match r { Ok(str) => println!("file : {}", str), Err(e) => panic!("err! (e : {:?}", e) }; } fn read_file(file: &str) -> Result<String, io::Error> { let mut s = String::new(); File::open(file)?.read_to_string(&mut s)?; Ok(s) }
물음표 연산자
? 를 위와 같이 사용할 때에는 반드시 Result 를 리턴하는 함수 내에서만 사용하여야 한다.