[Rust] 패턴 매치

18 minute read

복잡한 타입 구조 속 값을 비교하기 위한 문법

패턴 조합 요소

  • 리터럴 값
  • 분해한 배열, 열거형, 구조체, 튜플
  • 와일드카드
  • 임시값 (플레이스홀더)

패턴이 사용될 수 있는 모든 곳

match 키워드는 다음과 같은 구조를 가진다.

match  {
    패턴 => 표현,
    패턴 => 표현,
}

대응되는 값은 모든 경우를 빠짐없이 표현해야 한다.

값을 무시하고 싶으면 _ 패턴을 사용한다.

if let 조건 표현

match를 사용하려면 값이 가질 수 있는 모든 경우에 대응해야 한다.

한 가지 경우만 다루고 싶다면, if let 표현으로 더 짧은 코드를 작성할 수 있다.

if let, else if, else if let 표현을 섞어서 사용할 수 있다.

fn main() {
    let favorite_color: Option<&str> = None;
    let is_tuesday = false;
    let age: Result<u8, _> = "34".parse();

    if let Some(color) = favorite_color {
        println!("Using your favorite color, {}, as the background", color);
    } else if is_tuesday {
        println!("Tuesday is green day!");
    } else if let Ok(age) = age {
        if age > 30 {
            println!("Using purple as the background color");
        } else {
            println!("Using orange as the background color");
        }
    } else {
        println!("Using blue as the background color");
    }
}

위 코드는 Using purple as the background color를 출력한다.

if let Ok(age) = age에서 추출한 Ok 안의 age 변수는 조건문 아래의 새로운 스코프에서 사용할 수 있다.

그래서 if let Ok(age) = age && age > 30 이렇게 못 쓴다.

while let 조건 루프

let mut stack = Vec::new();

stack.push(1);
stack.push(2);
stack.push(3);

while let Some(top) = stack.pop() {
    println!("{}", top);
}

위 코드는 vector 비어서 None을 반환하기 전까지 스택의 내용을 출력한다.

for 루프

for x in y에서 for 바로 다음에 나오는 키워드가 패턴이다.

let v = vec!['a', 'b', 'c'];

for (index, value) in v.iter().enumerate() {
    println!("{} is at index {}", value, index);
}

enumerate 메서드는 반복자를 값과 인덱스로 구성된 튜플로 순회할 수 있게 한다.

let 구문

let (x, y, z) = (1, 2, 3);

위 코드는 튜플을 분해해서 각각을 변수에 대입한다.

let (x, y) = (1, 2, 3);

위와 같은 코드는 타입이 다르면 패턴도 다르기에 컴파일 에러난다.

_..를 추가해서 값을 무시할 수도 있다.

함수 매개변수

fn print_coordinates(&(x, y): &(i32, i32)) {
    println!("Current location: ({}, {})", x, y);
}

fn main() {
    let point = (3, 5);
    print_coordinates(&point);
}

매개변수도 패턴매칭으로 분해해서 사용할 수 있다

반증 가능성(Refutability)

반증 가능성은 패턴이 매칭에 실패할지의 여부를 뜻한다.

그에 반대인 반증 불가 패턴은 모든 값에 대응하는 패턴이다.

let x = 5;x가 모든 값에 대응하기에 실패할 수 없어서 반증 불가 패턴이다.

if let Some(x) = a_value;a_valueNone인 경우 실패하기에 반증 가능 패턴이다.

let 구문, for 루프는 반증 불가한 패턴만 허용한다.

if letwhile let 표현은 반증 가능 패턴만 허용한다.

이 둘의 구분은 컴파일러가 도와준다.

반증 불가 패턴이 필요한 곳에서 반증 가능 패턴을 쓰는 경우

let Some(x) = some_option_value;

반증 불가 패턴만 허용하는 let에 반증 가능 패턴을 사용했다.

None에 대응하지 않아서 컴파일 에러가 난다.

if let Some(x) = some_option_value {
    println!("{}", x);
}

let 대신 반증 가능 패턴을 허용하는 if let을 사용하면 해결된다.

반증 가능 패턴이 필요한 곳에서 반증 불가 패턴을 쓰는 경우

if let x = 5 {
    println!("{}", x);
};

if let은 반증 가능 패턴만 허용하지만, x = 5는 틀릴 수 없는, 반증 불가 패턴이기에 컴파일러 에러가 발생한다.

패턴 문법의 모든 것

모든 패턴 문법 나열

리터럴 매칭

let x = 1;

match x {
    1 => println!("one"),
    2 => println!("two"),
    3 => println!("three"),
    _ => println!("anything"),
}

x가 1이므로 one을 출력한다.

명명 변수 매칭

fn main() {
    let x = Some(5);
    let y = 10;

    match x {
        Some(50) => println!("Got 50"),
        Some(y) => println!("Matched, y = {:?}", y),
        _ => println!("Default case, x = {:?}", x),
    }

    println!("at the end: x = {:?}, y = {:?}", x, y);
}

match 문 스코프 속 Some(y)y는 밖에 y를 덮어쓴다.

Matched, y = 5를 출력한다.

다중 패턴

or을 뜻하는 | 구문을 이용하면 패턴을 여러개 매치할 수 있다.

let x = 1;

match x {
    1 | 2 => println!("one or two"),
    3 => println!("three"),
    _ => println!("anything"),
}

one or two를 출력한다.

...로 값의 범위 매칭하기

숫자와 char 값에만 사용할 수 있다.

아래는 숫자 값의 범위를 매칭하는 예시다.

let x = 5;

match x {
    1 ... 5 => println!("one through five"),
    _ => println!("something else"),
}

| 이용하면 1 | 2 | 3 | 4 | 5로 작성해야 하는 것을 1 ... 5로 간편하게 작성할 수 있다.

아래는 char 값의 범위를 매칭한다.

let x = 'c';

match x {
    'a' ... 'j' => println!("early ASCII letter"),
    'k' ... 'z' => println!("late ASCII letter"),
    _ => println!("something else"),
}

값을 해체하여 분리하기

구조체, 열거형, 튜플, 참조자 등을 해체(destructuring)할 수 있다.

구조체 해체하기

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7};

    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}

p 변수의 필드 x, y의 값을 각각 변수 ab에 대입한다.

패턴이 구조체 필드명과 일치하면 약칭 구문을 사용할 수 있다.

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x, y } = p;
    assert_eq!(0, x);
    assert_eq!(7, y);
}

필드 x, y의 값을 각각 같은 이름의 변수 xy에 대입한다.

구조체 패턴 일부에 리터럴 값을 이용해서 매치하기

fn main() {
    let p = Point { x: 0, y: 7 };

    match p {
        Point { x, y: 0 } => println!("On the x axis at {}", x),
        Point { x: 0, y } => println!("On the y axis at {}", y),
        Point { x, y } => println!("On neither axis: ({}, {})", x, y),
    }
}

각 갈래는 y가 0인 경우, x가 0인 경우, 나머지 경우를 매치한다.

열거형 해체

각 패턴은 내장된 데이터 정의가 일치해야 열거형을 해체할 수 있다.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => {
            println!("The Quit variant has no data to destructure.")
        },
        Message::Move { x, y } => {
            println!(
                "Move in the x direction {} and in the y direction {}",
                x,
                y
            );
        }
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => {
            println!(
                "Change the color to red {}, green {}, and blue {}",
                r,
                g,
                b
            )
        }
    }
}

참조자 해체

참조자를 가진 값은 패턴 내에서 &를 사용해 값을 해체한다.

let points = vec![
    Point { x: 0, y: 0 },
    Point { x: 1, y: 5 },
    Point { x: 10, y: -3 },
];

let sum_of_squares: i32 = points
    .iter()
    .map(|&Point { x, y }| x * x + y * y)
    .sum();

iter 메서드가 반환하는 값들은 실제 값이 아닌 참조자다.

위 코드에서 &를 빼면 타입 불일치 에러가 발생한다.

구조체와 튜플 해체

아래 같이 튜플과 구조체가 섞인 복잡한 패턴 해체도 가능하다.

let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });

패턴 내에서 값 무시하기

_는match`의 마지막 갈래에서 아무것도 안 하는 나머지 값을 매치할 때 유용하다.

값의 나머지를 무시하기 위헤 ..도 사용할 수 있다.

_를 이용해 전체 값 무시하기

아래 코드는 매개변수를 _로 무시한다.

fn foo(_: i32, y: i32) {
    println!("This code only uses the y parameter: {}", y);
}

fn main() {
    foo(3, 4);
}

특정 타입의 시그니처가 필요하지만, 함수 본문에서 매개변수가 필요없는 경우 유용하다.

중첩된 _를 이용해 값의 일부 무시하기

let mut setting_value = Some(5);
let new_setting_value = Some(10);

match (setting_value, new_setting_value) {
    (Some(_), Some(_)) => {
        println!("Can't overwrite an existing customized value");
    }
    _ => {
        setting_value = new_setting_value;
    }
}

println!("setting is {:?}", setting_value);

setting_valuenew_setting_value가 둘 다 Some 타입인지만 확인하고 내부 값은 무시한다.

let numbers = (2, 4, 8, 16, 32);

match numbers {
    (first, _, third, _, fifth) => {
        println!("Some numbers: {}, {}, {}", first, third, fifth)
    },
}

언더스코어를 여러번 사용할 수 있다.

두 번째 네 번째 요소만 무시한다.

사용하지 않는 변수 무시하기

러스트는 변수를 생성하고 사용하지 않으면 경고한다.

프로그램을 작성하는 중 변수를 미리 만들어 놓은 경우, 이런 경고가 귀찮을 수 있다.

fn main() {
    let _x = 5;
    let y = 10;
}

미사용 변수 앞에 언더스코어를 붙이면 경고를 끌 수 있다.

언더스코어로 시작하는 변수는 값이 바인드된다.

let s = Some(String::from("Hello!"));

if let Some(_s) = s {
    println!("found a string");
}

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

그냥 _와는 다르게 _s는 값이 바인드되어 s의 소유권을 가져간다.

println!("{:?}", s);에서 이미 옮겨진 값을 사용하려고 해서 에러가 발생한다.

..를 이용해 값의 나머지 부분 무시하기

struct Point {
    x: i32,
    y: i32,
    z: i32,
}

let origin = Point { x: 0, y: 0, z: 0 };

match origin {
    Point { x, .. } => println!("x is {}", x),
}

구조체의 필드 x만 사용하고 나머지 필드는 무시한다.

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (first, .., last) => {
            println!("Some numbers: {}, {}", first, last);
        },
    }
}

튜플의 첫 번째와 마지막 값만 매치하고 나머지는 모두 무시한다.

fn main() {
    let numbers = (2, 4, 8, 16, 32);

    match numbers {
        (.., second, ..) => {
            println!("Some numbers: {}", second)
        },
    }
}

위와 같이 ..를 두 곳에서 사용하는 경우 second를 특정할 수 없어서 에러가 발생한다.

refref mut 를 이용해 패턴 내에서 참조자 생성하기

ref로 참조자를 만들어 값의 소유권이 패턴 내의 변수로 이동하는 것을 막을 수 있다.

let robot_name = Some(String::from("Bors"));

match robot_name {
    Some(name) => println!("Found a name: {}", name),
    None => (),
}

println!("robot_name is: {:?}", robot_name);

위 코드는 robot_name의 소유권이 match 스코프 내 name로 이동했다.

이미 이동한 robot_name을 출력하려고 해서 컴파일 에러가 발생한다.

단순히 &를 사용해서 Some(name)Some(&name) 바꾼다고 해결이 되지 않는다.

패턴 내 &는 참조자를 생성하는게 아니라 이미 존재하는 참조자를 값으로 매치한다.

대신에 아래와 같이 변수 name 앞에 ref 키워드를 붙이면 패턴에 참조자를 생성할 수 있다.

let robot_name = Some(String::from("Bors"));

match robot_name {
    Some(ref name) => println!("Found a name: {}", name),
    None => (),
}

println!("robot_name is: {:?}", robot_name);

가변 참조자를 생성하려면 ref mut을 사용한다.

let mut robot_name = Some(String::from("Bors"));

match robot_name {
    Some(ref mut name) => *name = String::from("Another name"),
    None => (),
}

println!("robot_name is: {:?}", robot_name);

가변 참조자인 name의 값을 변경하기 위해서는 * 연산자로 역참조해야 한다.

매치 가드를 이용한 추가 조건

매치 가드는 match 갈래 뒤에 붙은 if 문이다.

다음 코드는 Some 타입이면서 값이 5 미만인 패턴을 매치한다.

let num = Some(4);

match num {
    Some(x) if x < 5 => println!("less than five: {}", x),
    Some(x) => println!("{}", x),
    None => (),
}

| 다중 패턴 뒤에도 매치 가드를 사용할 수 있다.

let x = 4;
let y = false;

match x {
    4 | 5 | 6 if y => println!("yes"),
    _ => println!("no"),
}

첫 번째 갈래는 4 | 5 | 6 패턴 전체에 해당되면서 ytrue인 경우를 뜻한다.

4 또는 5이거나, 6이면서 ytrue인 경우가 아니다.

@(at 연산자) 바인딩

@는 값이 패턴과 매치되는 동시에 해당 값을 가진 변수를 생성한다.

아래 코드는 Message::Helloid 필드가 3..7 범위내 있다면 그 값을 id_variable에 바인드한다.

enum Message {
    Hello { id: i32 },
}

let msg = Message::Hello { id: 5 };

match msg {
    Message::Hello { id: id_variable @ 3...7 } => {
        println!("Found an id in range: {}", id_variable)
    },
    Message::Hello { id: 10...12 } => {
        println!("Found an id in another range")
    },
    Message::Hello { id } => {
        println!("Found some other id: {}", id)
    },
}

Categories:

Updated: