[RUST] 6. 열거자와 패턴 매칭

    열거자(enumerations, enums)는 사용 가능한 값만 나열한 타입을 정의할 때 사용한다.

    열거자 정의하기

    IP주소를 예를 들어 보자. IP주소는 버전 4나 버전 6으로 두 가지가 표준으로 자리 잡고 있다.

    enum IpAddrKind {
        V4,
        V6,
    }

    열거된 값들을 열것값(variants)라고 한다.

    열거자의 값

    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
    
    fn route(ip_type: IpAddrKind) {}
    
    route(IpAddrKind::V4);
    route(six);

    열거자의 각 값은 식별자를 이용해 구분하며, 식별자와 값을 구분하기 위해 두 개의 콜론을 사용한다. 열거자를 매개변수로 갖는 함수를 정의할수도 있고, 열거자의 값을 이용해 호출 할 수도 있다.

    enum IpAddrKind {
        V4,
        V6,
    }
    
    struct IpAddr {
        kind: IpAddrKind,
        address: String,
    }
    
    let home = IpAddr {
        kind: IpAddrKind::V4,
        address: String::from("127.0.0.1"),
    };
    
    let loopback = IpAddr {
        kind: IpAddrKind::V6,
        adress: String::from("::1");
    }

    위는 구조체를 이용해 IP주소의 종류와 데이터를 저장한 예제이다. 하지만 열거자를 구조체에 넣지않고 열거자만을 이용하는 방법이 있다. 다음 예제를 보자.

    enum IpAddr {
        V4(String),
        V6(String),
    }
    
    let home = IpAddr::V4(String::from("127.0.0.1"));
    
    let loopback = IpAddr::V6(String::from("::1"));

    열거자 값에 데이터를 직접 지정하고 있다. 구조체 대신 열거자를 사용하게되면 열거자에 나열된 각각의 값을 서로 다른 타입과 다른 수의 연관 데이터를 보유할 수 있다는 점이다.

    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 블록을 이용해 열거자에도 메서드를 정의할 수 있다.

    impl Message {
        fn call(&self) {
            // 메서드 본문 작성
        }
    }
    
    let m = Message::Write(String::from("hello"));
    
    m.call();

    Option 열거자를 Null값 대신 사용할 때의 장점

    러스트는 널값이라는 개념이 없지만, 어떤 값의 존재 여부를 표현하는 열거자를 정의하고 있다. 이 열거자가 바로 Option< T >이며 표준 라이브러리에 다음과 같이 정의되어 있다.

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

    Option< T > 열거자는 프렐류드에 포함되어 있다. 열거값도 명시적으로 범위로 가져올 필요가 없다.

    let some_number = Some(5);
    let some_string = Some("a string");
    
    let absent_number: Option<i32> = None;

    Option< T >와 T는 서로 다른 타입이기 때무에 컴파일러는 유효한 값이 존재할 때는 Option< T >값을 사용하지 않도록 하고 있다. Option< T > 값을 사용하려면 열거자에 나열된 개별 값들을 처리할 코드를 작성해야 한다. match 표현식은 이런 코드를 쉽게 작성할 수 있는 '흐름 제어 연산자(control flow operator)'이다.

    match 흐름 제어 연산자

    macth는 러스트의 강력한 흐름제어 연산자이다. 이 연ㄴ산자는 일련의 패턴과 값을 비교해 일치하는 패턴에 지정된 코드를 실행한다. 패턴은 리터럴, 변수명, 와일드 카드를 비롯해 다양한 값으로 구성할 수 있다. match 표현식은 동전을 분리하는 기계라고 생각하면 된다.

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

    값을 바인딩하는 패턴

    match 표현식은 패턴에 일치하는 값의 일부를 바인딩 할 수 있다.

    #[derive(Debug)]
    enum UsState {
        Alabama, Alaska, ...
        // 생략
    }
    
    enum Coin {
        Penny,
        Nickel,
        Dime,
        Quarter(UsState),
    }
    
    fn value_in_cents(coin: Coin) -> u32 {
        match coin {
            Coin::penny => 1,
            Coin::Nickle => 5,
            Coin::Dime => 10,
            Coin::Quarter(state) => {
                println!("State quarter from {:?}!", state);
                25
            },
        }
    }

    위 코드는 match 표현식에서 패턴에 state변수를 추가해서 Coin::Quarter 열것값에 state 변수가 자동 바인딩 된다.

    Option< T >를 이용한 매칭

    Option< T >는 Coin 열거자의 사용처럼 match 표현식에도 사용할 수 있다.

    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_on(None);

    match는 반드시 모든 경우를 처리해야 한다

    fn plus_one(x: Option<i32>) -> Option<i32> {
        match x {
            come(i) => some(i + 1),
        }
    }

    위와 같은 코드를 작성했다면 None 값에 해당하는 경우를 처리하지 않았기 때문에 컴파일시 에러가 발생한다.

    자리지정자 _

    별도의 처리가 필요하지 않을 때는 _ 자리지정자로 대체하면 모든값이 일치함을 의미 하므로 나머지 모든 패턴에 대해 처리하게 된다.

    let some_u8_value = 0u8;
    match some_u8_value {
        1 => println!("one"),
        3 => println!("three"),
        5 => println!("five"),
        7 => println!("seven"),
        _ => (),
    }

    if let을 이용한 간결한 흐름 제어

    if let 문법은 하나의 패턴과 표현식을 등호 기호를 이용해 조합한다. match 표현식과 같은 방식으로 동작한다. if let 구문은 else 구문을 포함할 수 있다.

    if let Some(3) = some_u8_value {
        println!("three");
    }
    
    let mut count = 0;
    if let Coin::Quarter(state) = coin {
        println!("{:?}주의 25센트 동전!", state);
    } else {
        count += 1;
    }

    요약

    • 열거자를 사용해 사용자 정의 타입을 생성하는 방법
    • 표준 라이브러리의 Option< T > 타입을 이용해 오류 방지 기능 활용
    • match와 it let 표현식의 활용법
    반응형

    댓글

    Designed by JB FACTORY