Rust/Basic

[Rust Tutorial] 11 - Option

tyoon9781 2024. 11. 25. 22:53

 

 


 

Intro

Rust에서는 null값이 없는 대신에 None이 있습니다. 하지만 None은 null과 다른 점이 있습니다. null은 단독으로 사용할 수 있었지만, None의 경우 Option이란 Type을 사용해야 합니다. Option이란 None, 혹은 Some(Value)중 하나의 값임을 Enum으로 나타낸 Type입니다. 즉 Value를 바로 사용하기 전에 None인지 확인하고 사용하라는 의미이지요. None을 값으로 오해하지 않도록 하는 Rust의 방지책 중 하나입니다.

 

정리하자면 아래와 같습니다.

 

  • Option<T> : T Type(generic)을 가지는 Some(value)이거나  None인 값을 가지는 Enum.
  • Some(value) : Option<T>와 대응되는 값. T처럼 바로 사용할 수는 없다. value는 T와 대응된다. Some(value)를 value로 사용하려면 unwrap method를 사용해야 한다.
  • None: 값이 없음을 의미. Option<T>의 어느 형식에도 사용할 수 있다. 단지 T같은 Option이 없는 Type에서는 사용할 수 없다.

 

rust의 None을 다루기 위해서는 Option을 잘 다뤄야 합니다. 이 덕분에 None에 직접 값을 접근 하는 일은 없어졌지만 확실히 None이 입력될 수 있는 값에 대해서는 다른 언어들보다 한 차례 더 확인해야 하는 과정이 생긴 느낌은 듭니다. 하지만 확실히 runtime에 null과 관련된 Error는 걱정하지 않아도 됩니다.

 

 

예제를 통해 사용방법을 확인해 보겠습니다.

 

 


 

 

Code Example

 

 

Rust 1.79.0 (released 2024-06-13) 

fn main(){
    /* Option의 기본적인 구조. */
    enum _Option<T> {
        Some(T),
        None,
    }



    /* Option의 기본적인 선언 */

    let _a: Option<i32> = Some(10);    // Option<T>은 Some()으로 감싸서 표현한다.
    // let _a: Option<i32> = 10;       // [Error] 이것은 사용할 수 없다.
    let _a: Option<i32> = None;        // Option<T>은 None을 지정할 수 있다.



    /* Option의 value를 match로 처리 */

    // 앞으로 확인할 unwrap method도 match를 활용한 구조이다.
    fn check_option_value(some_value:Option<i32>){
        match some_value {
            Some(v) => println!("The value is {v}"),
            None => println!("The value is None"),
        }
    }

    check_option_value(Some(10));   // The value is 10
    check_option_value(None);       // The value is None



    /* unwrap()을 활용하여 Option의 value를 바로 얻어내기 */

    // some_value가 None이 아님을 확신할 때 사용한다.
    let some: Option<i32> = Some(10);     // Option<i32>에서 
    let _value: i32 = some.unwrap();      // i32를 바로 얻어낼 수 있다.

    let none: Option<i32> = None;         // 하지만 None이라면
    // let _value: i32 = none_value.unwrap();    // [Error] panic을 일으킨다. 반드시 None이 아닐 수 있는 값에만 unwrap을 사용하자.



    /* Option 관련 method 활용 */

    // unwrap_or(default) : Some이면 값을 반환, None이면 지정한 default값을 반환
    println!("unwrap_or : {}, {}", some.unwrap_or(20), none.unwrap_or(20));


    // unwrap_or_else(f) : Some이면 값을 반환, None이면 closure를 실행.
    // Option<T>에서 사용되는 closure의 return값은 T와 동일해야 함
    let c = || 30;
    println!("unwrap_or_else : {}, {}", some.unwrap_or_else(c), none.unwrap_or_else(c));


    // unwrap_or_default() : Some이면 값을 반환, None이면 type의 default값을 반환(예 : i32의 기본값은 0이다.)
    println!("unwrap_or_default : {}, {}", some.unwrap_or_default(), none.unwrap_or_default());


    // expect() : 값이 Some이면 값을 출력, None이면 panic message를 반환. panic이 expect될 때 사용한다.
    let _error_message = "None is not allowed";
    // [Error] panic을 일으킨다.
    // println!("expect : {}, {}", some.expect(error_message), none.expect(error_message));


    // is_some() : 값이 Some이면 true, None이면 false
    // is_none() : 값이 Some이면 false, None이면 true
    println!("is_some : {}, {}", some.is_some(), none.is_some());
    println!("is_none : {}, {}", some.is_none(), none.is_none());


    // map(f)                   : 값이 Some이면 closure를 실행, None이면 closure를 미실행
    // map_or_else(default)     : 값이 Some이면 closure를 실행, None이면 Type의 지정한 값을 반환
    println!("map : {}, {}", some.map(|v| v + 1).unwrap(), none.map(|v| v + 1).unwrap_or_default());


    // map_or(default, f)       : 값이 Some이면 closure를 실행, None이면 지정한 값을 반환
    let c = |v:i32| (v / 100) as f64;        // i32에서 f64를 return하는 closure 소환
    let value: Option<i32> = Some(12345) ;
    println!("map_or : {}", value.map_or(543.21, c));   // Some(i32)에 대해서 map으로 f64 type을 return하게 했다.
    

    // iter() : 데이터를 순회하면서 &T를 반환. Option type에서 None은 실행할 필요가 없을 때 사용할 수 있음
    let mut vec = Vec::new();
    let some_value: Option<i32> = Some(10);
    let none_value: Option<i32> = None;

    for v in some_value.iter(){
        println!("push value");     // 실행됨
        vec.push(*v)    
    }

    for v in none_value.iter(){
        println!("no push...");    // 실행되지 않음
        vec.push(*v)    
    }


    // (Option<T>와 직접적 관련은 없음) into_iter() : 데이터를 순회하면서 T의 소유권을 획득.
    let mut numbers = vec![1, 2, 3];
    numbers.push(4);
    for num in numbers.into_iter(){ // numbers는 소모됨
        println!("{}", num);    // 1, 2, 3, 4
    }
    // numbers.push(5);     // [Error] borrow of moved value: `numbers`. value borrowed here after move.


    // filter : closure를 통해 조건을 확인. 조건이 true인 것만 선택.
    let numbers = vec![1, 2, 3, 4, 5];
    let parsed_numbers: Vec<i32> = numbers
        .into_iter()
        .filter(|x| x % 2 == 0) // 짝수만 선택
        .map(|x| x * 10)         // 짝수는 10배
        .collect();                                             // Vec로 다시 모음

    println!("filter, map : {:?}", parsed_numbers); // [20, 40]


    // filter_map : filter의 조건 확인과 map의 변환기능을 합친 것. closure가 None을 return하면 선택되지 않음
    let inputs = vec!["123", "abc", "456", "def"];
    let numbers: Vec<i32> = inputs
        .into_iter()
        .filter_map(|x| x.parse::<i32>().ok()) // 숫자로 변환 가능한 값만 선택
        .collect();             // Vec로 다시 모음

    println!("{:?}", numbers); // [123, 456]

    

    /* 물음표를 활용하여 shortcut 추출 */

    // "?" : Option<T> -> T 추출, 만약 None이라면 return None; 바로 동작.
    fn get_first_char_shortcut(s: Option<&str>) -> Option<char> {
        // if s.is_some(){ let text = s.unwrap();} else {return None} 를 물은표로 나타낼 수 있다.
        let text: &str = s?;      

        let first_char: char = text.chars().next()?;

        Some(first_char)
    }

    println!("get_first_char_shortcut : {:?}, {:?}", get_first_char_shortcut(Some("text")), get_first_char_shortcut(None));
    

}

 

 

 


 

* reference

 - https://doc.rust-lang.org/book/