6. 열거형과 패턴 매칭

열거형은 다른 언어들에서도 볼 수 있는 특징이지만, 각 언어마다 열거형으로 할 수 있는 것들이 다릅니다. 러스트의 열거형은 F#, OCaml, Haskell 과 같은 함수형 언어의 대수 데이터 타입과 가장 비슷합니다.

6.1. 열거형 정의하기

  • 간단한 열거형
enum IpAddrKind { V4, V6, }
let four = IpAddrKind::V4; let six = IpAddrKind::V6;
  • 연관 데이터가 있는 열거형
enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));
 
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } impl Message { fn call(&self) { // 메소드 내용은 여기 정의할 수 있습니다. } } let m = Message::Write(String::from("hello")); m.call();
 

Option 열거형이 Nuul값보다 좋은 이유

  • null 을 고안한 Tony Hoare 의 "Null 참조 : 10 억 달러의 실수"
나는 그것을 나의 10억 달러의 실수라고 생각한다. 그 당시 객체지향 언어에서 처음 참조를 위한 포괄적인 타입 시스템을 디자인하고 있었다. 내 목표는 컴파일러에 의해 자동으로 수행되는 체크를 통해 모든 참조의 사용은 절대적으로 안전하다는 것을 확인하는 것이었다. 그러나 null 참조를 넣고 싶은 유혹을 참을 수 없었다. 간단한 이유는 구현이 쉽다는 것이었다. 이것은 수없이 많은 오류와 취약점들, 시스템 종료를 유발했고, 지난 40년간 10억 달러의 고통과 손실을 초래했을 수도 있다. -Tony Hoare 의 "Null 참조 : 10 억 달러의 실수"-
 
  • Rust에는 Null 값이 없다.
  • 대신 Option<T>가 있다.
enum Option<T> { Some(T), None, }
 
let some_number = Some(5); let some_string = Some("a string"); let absent_number: Option<i32> = None;
 
let x: i8 = 5; let y: Option<i8> = Some(5); let sum = x + y;
error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not satisfied --> | 7 | let sum = x + y; | ^^^^^ |
 

6.2. match 흐름 제어 연산자

enum Coin { Penny, Nickel, Dime, Quarter, } fn value_in_cents(coin: Coin) -> u32 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter => 25, } }
 

Option 을 이용하는 매칭

fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => None, Some(i) => Some(i + 1), } } let five = Some(5); let six = plus_one(five); let none = plus_one(None);
 
 

_ 와일드 카드(placeholder)

let some_u8_value = 0u8; match some_u8_value { 1 => println!("one"), 3 => println!("three"), 5 => println!("five"), 7 => println!("seven"), _ => (), }
💡
이렇게 보면 Rust의 match 연산자는 마치
🌱
씨앗
값 선택문을 닮았다.
 

6.3. if let을 사용한 간결한 흐름 제어

  • match를 이용한 코드
let some_u8_value = Some(0u8); match some_u8_value { Some(3) => println!("three"), _ => (), }
let mut count = 0; match coin { Coin::Quarter(state) => println!("State quarter from {:?}!", state), _ => count += 1, }
 
  • if let을 사용한 코드
if let Some(3) = some_u8_value { println!("three"); }
let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {:?}!", state); } else { count += 1; }
 
💡
SwiftSwift
Swift
if let이랑 생긴 것은 같은데 뭔가 느낌이 좀 다르다. Swift에서는 = 오른쪽의 값을 평가하여 그 값이 None이 아니면 = 왼쪽의 식별자에 대입하는 문항인데, Rust는 = 양쪽이 몹셔널일 필요도 없고 그냥 일반적인 enum이기만 하면 된다. 특별히 옵셔널에서 값을 빼내고 싶으면 if let some(x) = .... 식으로 하면 우변의 값을 평가하여 왼쪽 x에 대입하기는 한다.
  • Swift와 Rust의 if let은 비슷한 용도로 쓰이지만 표시하는 방식이 영 다르다.
 
// Make `optional` of type `Option<i32>` let optional = Some(7); match optional { Some(i) => { println!("This is a really long string and `{:?}`", i); // ^ Needed 2 indentations just so we could destructure // `i` from the option. }, _ => {}, // ^ Required because `match` is exhaustive. Doesn't it seem // like wasted space? };
fn main() { // All have type `Option<i32>` let number = Some(7); let letter: Option<i32> = None; let emoticon: Option<i32> = None; // The `if let` construct reads: "if `let` destructures `number` into // `Some(i)`, evaluate the block (`{}`). if let Some(i) = number { println!("Matched {:?}!", i); } // If you need to specify a failure, use an else: if let Some(i) = letter { println!("Matched {:?}!", i); } else { // Destructure failed. Change to the failure case. println!("Didn't match a number. Let's go with a letter!"); } // Provide an altered failing condition. let i_like_letters = false; if let Some(i) = emoticon { println!("Matched {:?}!", i); // Destructure failed. Evaluate an `else if` condition to see if the // alternate failure branch should be taken: } else if i_like_letters { println!("Didn't match a number. Let's go with a letter!"); } else { // The condition evaluated false. This branch is the default: println!("I don't like letters. Let's go with an emoticon :)!"); } }
 
  • 열거형에서도 사용 가는한 if let
// This enum purposely doesn't #[derive(PartialEq)], // neither we implement PartialEq for it. That's why comparing Foo::Bar==a fails below. enum Foo {Bar} fn main() { let a = Foo::Bar; // Variable a matches Foo::Bar if Foo::Bar == a { // ^-- this causes a compile-time error. Use `if let` instead. println!("a is foobar"); } }
 
  • 그래서 최종적으로
    SwiftSwift
    Swift
    RustRust
    Rust
    의 차이점을 보여주는 아래의 코드를 제시한다.
var ve = [1, 2, 3, 4] if let x = ve.popLast() { print("Popped \(x)") }
 
#Rust let mut ve = vec![1, 2, 3, 4]; if let Some(x) = ve.pop() { println!("Popped {:?}", x); }