본문 바로가기

Rust

Rust 열거형과 패턴 매칭

[1. 개요]

열거형은 하나의 타입이 가질 수 있는 값들을 열거 함으로써 타입을 정의할 수 있도록 한다.

Option 이라는 열거형의 존재 (boost::optional 처럼 사용?)

열거형 값에 따른 분기를 위해 match 표현식과 , if let 구문

 

 

[2. 열거형 정의]

enum IpAddr {
    V4(String), // 타입을 명시할 수 있다.
    V6(String),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 }, // 익명 구조체
    Write(String),           // 문자열
    ChangeColor(i32, i32, i32), // 튜플
}

fn main() {
    let home = IpAddr::V4(String::from("127.0.0.1"));
    let loopback = IpAddr::V6(String::from("::1"));

    home = loopback; // 같은 열거형에 속하지만, 타입이 달라 컴파일 에러 발생
}

 

열거형 variant 에 어떤 종류의 데이터라도 넣을 수 있습니다.

Message 열거형은 하나의 타입으로 관련된 함수를 정의 할 수 있다.

=> 타입별로 함수를 정의할 필요 없다.

 

 

[3. Option 열거형]

Option<T> 열거형은 많이 사용되며,

값이 있거나 없을 수 있는 상황을 표현 할 수 있다.

=> 컴파일러가 발생할 수 있는 모든 경우를 처리했는지 체크 할 수 있다.

 

또한, 기본적으로 포함되어 있어서 명시적으로 가져오지 않아도 사용 할 수 있다.

enum Option<T> {
    Some(T),
    None,
}

 

fn main() {
    let some_number = Some(5);
    let some_string = Some("a string");

    let absent_number: Option<i32> = None; 

    let zz = None; // 컴파일 에러 발생
}

Some 이 아닌 None 을 사용한다면, Option<T> 가 어떤 타입인지 명시 할 필요가 있다.

 

Option<T> 가 Null 보다 나은 이유는?

=> Option<T> 와 T 는 서로 다른 타입이다.

=> i8 타입과 Option<i8> 간 덧셈은 컴파일 에러를 유발한다.

==> 덧셈을 위해 Some 인지 None 인지 확인해야 한다.

==> None 인 경우 처리를 반드시 하게 된다.

 

 

[4. match 흐름 제어 연산자]

match 라는 흐름 제어 연산자는 일련의 패턴에 대해 어떤 값을 비교한 뒤, 

어떤 패턴에 매치되었는지를 바탕으로 코드를 수행하도록 해준다.

 

match 의 힘은 패턴의 표현성으로부터 오며, 컴파일러는 모든 가능한 경우가 다루어지는지 검사한다.

 

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u32 {
    match coin {
        Coin::Penny => {
            println!("Lucky penny!");
            1
        },
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

bool 값을 사용해야하는 if ~ else 와 달리 타입에 영향을 받지 않는다.

 

패턴 다음에는 => 연산자가 오며, 

매칭되는 패턴을 발견하면, 패턴과 연관된 코드가 실행된다.

 

 

[5. Option<T> 를 이용한 매칭]


fn main() {
    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);

    println!("{}", match six {
        //None => 0, // 컴파일 에러
        Some(i) => i
    });
}

match 내 에서 Some 내 값을 i (변수 이름, not keyword) 에 바인드 할 수 있다.

 

None 에 대한 처리가 없는 경우, 컴파일 에러가 발생한다.

 

 

[6. 변경자, placeholder]

fn main() {
    let some_u8_value = 0u8;
    match some_u8_value {
        1 => println!("one"),
        3 => println!("three"),
        5 => println!("five"),
        256 => println!("seven"), // 컴파일 에러
        _ => (),
    }
}

match 내 모든 값을 나열 할 필요가 없는 경우,

_ (placeholder) 를 사용하여, 명시할 수 있다.

 

또한, 위 예제에서 some_u8_value 는 unsigned 8bit 이므로,

256 을 표현 할 수 없다.

여기서 컴파일 에러가 발생한다.

 

 

[7. if let 을 사용한 간결한 흐름제어]

fn main() {
    let some_u8_value = Some(3u8); // unsigned 8bit: 0
    
    if let Some(0u8) = some_u8_value {
        println!("three");
    } else {
        println!("four");
    }

    println!("{:?}", some_u8_value);

    if let some_u8_value = Some(5) { // 잘못된 사용 방법
        println!("inner {:?}", some_u8_value);
    }

    println!("{:?}", some_u8_value);
   
}


// 출력결과
// four
// Some(3)
// inner Some(5)
// Some(3)

if let 은 match 가 강제했던 하나도 빠짐없는 검사를 할 수 없게한다.

그러나 보일러 플레이트 코드를 덜 쓰게 한다.

적절한 trade off 가 있는 상황이므로, 적절히 사용해야 한다.

 

또한, 사용 시 순서를 주의하도록 한다.

의도하지 않은 실행이 발생 할 수 있다.

(let 으로 인한 변수 할당을 참으로 보기 때문인가?)

fn main() {
    let some_u8_value = Some(3u8); // unsigned 8bit: 0
       
    println!("{:?}", some_u8_value);
    {
        let some_u8_value = 111;
        println!("{}", some_u8_value);
    } 
    println!("{:?}", some_u8_value);
}

// 출력
// Some(3)
// 111
// Some(3)

 

'Rust' 카테고리의 다른 글

Rust mod, pub, use  (0) 2022.08.24
Rust 구조체  (0) 2022.08.20
슬라이스  (0) 2022.08.18
참조자와 빌림  (0) 2022.08.18
소유권  (0) 2022.08.17