시뻘건 개발 도전기

러스트 21 - 예외처리 #2 : Result<T, E> 본문

프로그래밍/RUST

러스트 21 - 예외처리 #2 : Result<T, E>

시뻘건볼때기 2019. 4. 14. 16:47
반응형

 

panic! 매크로가 복구 불가능한 에러를 처리하기 위함이라면, 복구 가능한 에러 처리를 위한 녀석은 Result<T, E>가 있다.

 

enum Result<T, E> {
    Ok(T),
    Err(E),
}

Result의 핵심!!!

  • T와 E는 제네릭 타입 파라미터다.
    • T는 성공한 후에 Ok variant 내에 반환될 값의 타입
    • E는 실패한 후에 Err variant 내에 반환될 에러 타입

파일을 가지고 오는 코드를 보자.

use std::fs::File;

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

    if file.is_ok() {
        println!("SUCCESS!!! : {:?} ", file.ok());
    } else {
        println!("FAILE!!! : {:?}", file.err());
    }
}

파일이 없으면 다른 파일을 읽는 다거나, 각각의 로직을 다르게 태울 수 있다는 이야기가 된다.

 

 

에러를 맵핑시켜보자.

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

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

    let f = match f {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("파일 생성에 실패 : {:?}", e),
            },
            other_error => panic!("파일 로드 중에 문제 발생 : {:?}", other_error),
        },
    };
}

match를 사용하면 이러한 코드구현도 가능하다.

File::open이 반환하는 값의 타입은 io::Error이며 표준 라이브러리에서 제공하는 구조체다.

이 구조체에는 io::ErrorKind 값을 얻기 위해 kind라는 메소드가 있다.

io::ErrorKind는 표준 라이브러리에서 제공되며 io 작업으로 인해 발생할 수 있는 다양한 종류의 에러를 표현하는 variant를 가진 열거형이다. 상위 코드에서 사용하는 variant는 io::ErrorKind::NotFound라는 녀석이다.

 

 

1. 에러가 났을 때 panic을 위한 unwrap, expect

match는 충분히 사용 가능하지만, 의도를 정확하게 파악하기 어려울 수 있다.

result<T, E> 타입은 다양한 작업 수행을 위해 여러가지의 헬퍼 메소드가 정의되어 있다.

 

1. unwrap

match 표현식 처럼 구현되는 숏컷 메소드라고 한다.

  • 결과 값이 Ok variant인 경우 unwrap은 Ok 내부의 값을 반환한다.
  • 결과 값이 Err variant인 경우 unwrap은 panic! 매크로를 호출한다.

2. expect

unwrap과 유사하지만, panic! 매크로의 에러 메시지를 개발자 맘대로 할 수 있도록 해준다.

이렇게...!!!

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("파일 오픈 실패!!!");
}

 

2. 에러 전파

구현이 실패 할 수도 있는(에러 발생) 함수를 작성할 때, 오류 처리를 해당 함수 내에서 할지, 혹은 호출한 녀석이 할지에 대한 고민은 누구나 했을 것이다. 

use std::io;
use std::io::Read;
use std::fs::File;

fn read_userName_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    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_userName_from_file의 리턴 값은 Result. Ok라면 String, Err라면 io::Error를 리턴할 것이다.

러스트는 에러 전파 패턴이 굉장히 흔하게 발생되기 때문에, 물음표 연산자("?")를 제공한다.

 

use std::io;
use std::io::Read;
use std::fs::File;

fn read_userName_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

에러를 호출한 쪽으로 리턴하는 함수가 이렇게 간단해졌다.

Result 값 뒤에 ?는 이전 match 표현식과 같은 방식으로 동작 되도록 정의되어 있다. 만약 Result의 값이 Ok이면 Ok 내부의 값이 이 표현식에서 반환되고 프로그램은 계속 진행 될 것이다.

반대로, 값이 Err이면 오류 값이 리턴될 것이다.

반응형
Comments