본문 바로가기
개발/러스트 (Rust)

Rust 기본 : 결과 열거형 (Result enum)

by snowoods 2024. 8. 21.

https://rustacean.net/

 

Rust 기본 : 결과 열거형 (Result enum)

 

Rust에서 유효한 값을 반환하거나 에러 값을 반환하려면

Result enum을 사용할 수 있습니다.

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


Result enum에는 두 가지 변형이 있습니다. 

하나는 일반적인 값을 포함하는 Ok 변형이고, 

다른 하나는 에러 값을 가지는 Err 변형입니다.

Result enum과 그 변형들은 std::prelude에 있습니다. 
std::prelude는 Rust가 모든 Rust 프로그램에 자동으로 가져오는 것들의 목록입니다.
따라서 추가 코드 없이도 이들을 사용할 수 있습니다.

std::prelude
https://doc.rust-lang.org/std/prelude/

 

std::prelude - Rust

§The Rust Prelude Rust comes with a variety of things in its standard library. However, if you had to manually import every single thing that you used, it would be very verbose. But importing a lot of things that a program never uses isn’t good either.

doc.rust-lang.org

 


 

enum 인스턴스 execute() 메소드 결과로 사용하기.

enum Operation {
    Add(i32, i32),
    Mul(i32, i32),
    Sub { first: i32, second: i32 },
    Div { divident: i32, divisor: i32 },
}

impl Operation {
    fn execute(self) -> Result<i32, String> { // Ok(i32) or Err(String)
        match self {
            Self::Add(a, b) => Ok(a + b),
            Self::Mul(a, b) => Ok(a * b),
            Self::Sub { first, second } => Ok(first - second),
            Self::Div { divident, divisor } => {
                if divisor == 0 {
                    Err(String::from("Can not divide by zero"))
                } else {
                    Ok(divident / divisor)
                }
            }
        }
    }
}

fn main() {
    let user_input = Operation::Div {
        divident: 20,
        divisor: 0,
    };
    
    match user_input.execute() {
        Ok(res) => println!("Result: {res}"),
        Err(e) => println!("Error: {e}"),
    }
}


Unit 타입('()') 사용하기.

fn greet(name: &str) -> Result<(), String> {
    if name.len() > 0 {
        println!("Hello {name}!");
        Ok(())
    } else {
        Err("Empty name provided".to_string())
    }
}

fn main() {
    let name = "Tom";
    if let Err(e) = greet(name) {
        println!("Error: {e}");
    }
}


Resut -> Option -> match 연쇄 사용하기.

ok() 메소드는 Result<T, E> -> Option<T> 로 변환.

값이 있으면 Some, 에러일 경우 None 리턴.

 

에러는 무시하고 결과의 유무만 확일할 때 사용한다.

fn main() {
    let username = get_username(1);
    if let Some(name) = username {
        match name.as_str() {
            "Tom" => println!("Hello, {}!", name),
            "Jane" => println!("Good morning! {}.", name),
            _ => println!("Unknown name: {}", name),
        }
    }    
}

fn get_username(user_id: u32) -> Option<String> {
    let query = format!("SELECT username FROM users WHERE id={user_id}");
    let db_result = query_db(query);

    // Converts from Result<T, E> to Option<T>
    //  - Converts self into an Option<T>, consuming self, 
    //    and discarding the error, if any.
    db_result.ok()
}

fn query_db(query: String) -> Result<String, String> {
    if (query.is_empty()) {
        Err(String::from("Query string is empty!"))
    } else {
        Ok(String::from("Tom"))
    }
}

 

Result, Option, match 연쇄 사용이 번거로워 보일 수 있다.

그러나 C++을 사용하면서 함수 결과에 대해 어떻게 처리할지 고민해본 사람이라면

러스트는 상당히 체계적으로 결과에 대한 처리 방식을 기본 제공 한다.

 

여기에 에러 처리와 함수 연쇄까지 합쳐지면 

상당히 직관적이고 완벽한 코드를 생성할 수 있다.

러스트는 그 방향의 가치를 인지하고 분명하게 제시한다.