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/