본문 바로가기
Rust

Rust 설치부터 실행까지 (match, if let) - 14

by lms0806 2025. 1. 12.
728x90
반응형

오늘은 Rust의 match, if let에 대해 알아보고자 합니다.

match 제어 흐름 구조

rust는 match라고 불리는 매우 강력한 제어 흐름 연산자를 가지고 있는데, 일련의 패턴에 대해 어떤 값을 비교한 뒤 어떤 패턴에 매칭되었는지를 바탕으로 코드를 수행하도록 해줍니다.

 

패턴은 리터럴 값, 변수명, 와일드카드 등 다양한 것으로 구성될 수 있습니다.

 

match의 힘은 패턴의 표현성으로부터 오며 컴파일러는 모두 가능한 경우가 처리되는지 검사합니다.

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

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

rust의 match는 다른 언어들의 switch문과 비슷합니다.

 

match에는 갈래(arm)들이 있고, 하나의 갈래는 패턴과 코드 두 부분으로 이루어져 있습니다.

 

예시에서는 첫 번째 갈래에는 값 Coin::Penny로 되어있는 패턴이 있고, 그 뒤에 패턴과 실행되는 코드를 구분해주는 => 연산자가 있습니다.

 

match표현식이 실행될 때, 결과값을 각 갈래의 패턴에 대해서 순차적으로 비교하다가 만일 어떤 패턴이 그 값과 매칭되면, 그 패턴과 연관된 코드가 실행됩니다.

 

만일 그 패턴이 값과 매칭되지 않는다면, 동전 분류기와 비슷하게 다음 갈래로 실행을 계속합니다.

 

여러 줄의 코드를 실행시키고 싶다면 중괄호를 사용하고, 그렇게 되면 갈래 뒤에 붙이는 쉼표는 옵션이 됩니다.

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

값을 바인딩하는 패턴

매치 갈래의 또 다른 유용한 기능은 패턴과 매칭된 값들의 일부분을 바인딩할 수 있다는 것입니다.

#[derive(Debug)] // so we can inspect the state in a minute
enum UsState {
    Alabama,
    Alaska,
    // --생략--
}

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter(UsState),
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter(state) => {
            println!("State quarter from {:?}!", state);
            25
        }
    }
}

만약게 value_in_cents(Coin::Quarter(UsState::Alaska))를 호출했다면, coinCoin::Quarter(UsState::Alaska)가 됩니다.

 

state에 대한 바인딩은 값 UsState::Alaska가 되고, 이 바인딩은 println! 표현식에서 사용할 수 있고, 따라서 Quarter에 대한 Coin열거형 배리언트로부터 주에 대한 내부 값을 얻습니다.

Option를 이용하는 매칭

이전에 Option 값을 사용하려면 Some일 떄 실행돼서, Some 내의 T 값을 얻을 수 있는 코드가 필요하다고 하였습니다.

Option의 배리언트를 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_one(None);

plus_one(five)가 호출될 때, plus_one 본문 안에 있는 변수 x는 값 Some(5)를 갖게 될 것이고, Some(i)에 매칭되어 최종적으로 6을 담은 새로운 Some값으 생성합니다.

 

plue_one(None)의 경우 match안에 들어오면 첫 번째 갈래와 비교하여 매칭되어 None 값을 반환합니다.

매치는 철저합니다.

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

다음과 같이 작성된 코드의 경우 에러를 출력하게 됩니다.

error[E0004]: non-exhaustive patterns: `None` not covered
 --> src/main.rs:3:15
  |
3 |         match x {
  |               ^ pattern `None` not covered
  |
note: `Option<i32>` defined here
 --> /rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/option.rs:518:1
  |
  = note:
/rustc/d5a82bbd26e1ad8b7401f6a718a9c57c96905483/library/core/src/option.rs:522:5: not covered
  = note: the matched value is of type `Option<i32>`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
  |
4 ~             Some(i) => Some(i + 1),
5 ~             None => todo!(),
  |

For more information about this error, try `rustc --explain E0004`.
error: could not compile `enums` due to previous error

rust의 매치는 발생할 수 있는 경우 중 놓친 게 있음을 아는 것은 물론, 어떤 패턴을 놓쳤는가도 알고 있죠.

 

None 케이스를 다루는 것을 깜빡하더라도 rust가 알아채고 알려주기 때문에, 앞서 설명했던 null일지도 모를 값을 가지고 있어서 발생할 수 있는 실수를 불가능하게 만듭니다.

포괄 패턴과 _ 자리 표시자

열거형을 사용하면서 특정한 몇 개의 값들에 대해 특별한 동작을 하지만, 그 외의 값들에 대해서는 기본 동작을 취하게 할 수 있습니다.

    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        other => move_player(other),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn move_player(num_spaces: u8) {}

나머지 모든 가능한 값을 다루는 마지막 갈래에 대한 패턴은 other 라는 이름을 가진 변수입니다.

 

u8이 가질 수 있는 모든 값을 나열하지 않았음에도 이 코드는 컴파일 되는데, 그 이유는 특별하게 나열되지 않은 나머지 모든 값에 대해 마지막 other 패턴이 매칭되기 때문입니다.

 

이러한 포괄(catch-all) 패턴은 match의 철저함을 만족시킵니다.

 

포괄적인 갈래는 마지막에 위치시켜야 하며, 만약 포괄 패턴 뒤에 갈래를 추가하면 rust는 이에 대해 경고를 줍니다.

 

만약 match에서 3이나 7같은 값이 아닌 다른 값이 들어온 경우, 그 값을 활용하지 않는다면 other대신에 _를 사용할 수 있습니다.

    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => reroll(),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}
    fn reroll() {}

또한 3이나 7같은 값이 아닌 경우, 아무일도 일어나지 않도록 하기 위해서는 다음과 같이 가능합니다.

    let dice_roll = 9;
    match dice_roll {
        3 => add_fancy_hat(),
        7 => remove_fancy_hat(),
        _ => (),
    }

    fn add_fancy_hat() {}
    fn remove_fancy_hat() {}

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

if let 문법은 iflet을 조합하여 하나의 패턴만 매칭시키고 나머지 경우는 무시하도록 값을 처리하는 간결한 방법을 제공합니다.

    let config_max = Some(3u8);
    match config_max {
        Some(max) => println!("The maximum is configured to be {}", max),
        _ => (),
    }

match를 활용하여 다음과 같이 표현한 경우, if let을 이용하여 같은 동작을 하면서, 코드를 더 짧게 표현할 수 있습니다.

    let config_max = Some(3u8);
    if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
    }

if let=로 구분된 패턴과 표현식을 입력받아 match와 동일한 방식으로 작동되는데, 여기서 표현식은 match에 주어지는 것이고 패턴은 이 match의 첫 번째 갈래와 같습니다.

 

if let을 이용하면 덜 타이핑하고, 덜 들여쓰기 하고, 보일러 플레이트 코드를 덜 쓰게 됩니다.

 

if let은 한 패턴에 매칭될 때만 코드를 실행하고 다른 경우 무시하는 match문을 작성할 때 사용하는 문법 설탕(syntax sugar)이라고 생각하시면 됩니다.

 

if let과 함께 elsematch 표현식에서 사용한 _를 사용하여 표현 가능합니다.

    let mut count = 0;
    match coin {
        Coin::Quarter(state) => println!("State quarter from {:?}!", state),
        _ => count += 1,
    }

물론 다음과 같이 작성도 가능합니다.

    let mut count = 0;
    if let Coin::Quarter(state) = coin {
        println!("State quarter from {:?}!", state);
    } else {
        count += 1;
    }

프로그램이 match로 표현하기에 너무 장황한 로직을 하는 경우라면, if let를 활용하시면 됩니다.

정리

  1. 타 언어의 switch문이 rust에서는 match문이 된다.
  2. 특정 case가 아닌 경우에 해당 변수를 사용해야 한다면 other로, 변수를 사용하지 않아도 된다면 _으로 표현하여 사용이 가능합니다.
  3. 하나의 조건문만 필요로 하는데 match로 사용하기에 너무 코드가 길고 불편하다면 if let을 활용하여 가능합니다.
728x90
반응형

댓글