hyunsu blog

홈강좌Rust 입문·중급 47편 중 25편째

Rust match — 경우를 빠짐없이 다뤄야 컴파일된다 (E0004)

목차 · 4
  1. 01경우마다 갈래를 쓴다
  2. 02경우를 빠뜨리면 E0004
  3. 03경우에 붙은 데이터를 꺼낸다
  4. 04실습 — 나머지는 _ 로

Rust 의 match 는 값이 어느 경우인지 위에서부터 맞춰 보고, 맞는 갈래를 실행한다. 열거형의 경우를 하나라도 빠뜨리면 E0004 로 컴파일이 멈춘다. 경우에 붙은 데이터는 갈래에서 이름을 붙여 꺼내고, 나머지를 한꺼번에 받을 때는 _ 를 쓴다.

01 경우마다 갈래를 쓴다

24편의 신호등마다 기다리는 초를 정해 보자.

rust
enum Light {
    Red,
    Yellow,
    Green,
}

fn seconds(light: Light) -> u32 {
    match light {
        Light::Red => 30,
        Light::Yellow => 3,
        Light::Green => 25,
    }
}

fn main() {
    println!("red: {}s", seconds(Light::Red));
    println!("yellow: {}s", seconds(Light::Yellow));
    println!("green: {}s", seconds(Light::Green));
}
text
   Compiling light v0.1.0 (~/projects/light)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/light`
red: 30s
yellow: 3s
green: 25s
  • match light { … } 안에 경우 => 값, 을 늘어놓는다. 한 줄 하나가 갈래(arm)다.
  • 위에서부터 차례로 맞춰 보고, 처음 맞는 갈래 하나만 실행한다.
  • match 도 12편의 if 처럼 값을 낸다. 맞은 갈래의 값이 match 전체의 값이고, 여기서는 그것이 함수의 마지막 식이라 반환값이 됐다.

02 경우를 빠뜨리면 E0004

Yellow 갈래를 지워 보자.

rust
enum Light {
    Red,
    Yellow,
    Green,
}

fn seconds(light: Light) -> u32 {
    match light {
        Light::Red => 30,
        Light::Green => 25,
    }
}

fn main() {
    println!("{}", seconds(Light::Red));
}
text
error[E0004]: non-exhaustive patterns: `Light::Yellow` not covered
  --> src/main.rs:8:11
   |
 8 |     match light {
   |           ^^^^^ pattern `Light::Yellow` not covered
   |
note: `Light` defined here
  --> src/main.rs:1:6
   |
 1 | enum Light {
   |      ^^^^^
 2 |     Red,
 3 |     Yellow,
   |     ------ not covered
   = note: the matched value is of type `Light`
help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern or an explicit pattern as shown
   |
10 ~         Light::Green => 25,
11 ~         Light::Yellow => todo!(),
  1. 무엇 — 패턴이 빠짐없지 않다(non-exhaustive). Light::Yellow 를 다루지 않았다.
  2. 어디 — 8번째 줄의 match light. note: 가 정의를 보여 주며 3번째 줄의 Yellow 를 not covered 로 짚는다.
  3. 고치는 법 — help: 가 Light::Yellow => todo!(), 갈래를 더하라고 한다. ~ 가 붙은 줄이 고친 모양이다. todo!() 는 "아직 안 만들었다" 는 자리 표시로, 실제로는 3 처럼 알맞은 값을 넣는다.

이것이 match 의 힘이다. 나중에 Light 에 경우를 하나 더하면, 그 경우를 다루지 않은 match 를 컴파일러가 전부 찾아 준다. if 를 여러 번 늘어놓았다면 빠뜨린 곳을 사람이 찾아야 한다.

03 경우에 붙은 데이터를 꺼낸다

24편 끝의 Shape 로 넓이를 구해 보자.

rust
enum Shape {
    Circle(f64),
    Rect(f64, f64),
    Dot,
}

fn area(shape: Shape) -> f64 {
    match shape {
        Shape::Circle(r) => 3.14 * r * r,
        Shape::Rect(w, h) => w * h,
        Shape::Dot => 0.0,
    }
}

fn main() {
    println!("{}", area(Shape::Circle(2.0)));
    println!("{}", area(Shape::Rect(3.0, 4.0)));
    println!("{}", area(Shape::Dot));
}
text
   Compiling shape v0.1.0 (~/projects/shape)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/shape`
12.56
12
0

Shape::Circle(r) 갈래는 원에 붙은 반지름을 r 이라는 이름으로 꺼낸다. Shape::Rect(w, h) 는 둘을 w·h 로 꺼낸다. 8편에서 튜플을 let (a, b) = … 로 풀던 것과 같은 모양이다. 꺼낸 이름은 그 갈래 안에서만 쓴다.

04 실습 — 나머지는 _ 로

정수처럼 경우가 셀 수 없이 많으면 모두 쓸 수 없다. 이때 _ 가 "나머지 전부" 를 받는다.

rust
fn main() {
    for roll in [1, 3, 6] {
        let label = match roll {
            1 => "one",
            6 => "six",
            _ => "other",
        };
        println!("{roll}: {label}");
    }
}
text
   Compiling dice v0.1.0 (~/projects/dice)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/dice`
1: one
3: other
6: six

_ 갈래는 맨 끝에 둔다. 위에서부터 맞춰 보니, 맨 앞에 두면 모든 값이 거기서 걸린다. 위 코드에서 _ => "other", 줄을 지우면 이 편의 E0004 가 다시 난다. 이번에는 무엇 줄에 어떤 숫자가 빠졌다고 나오는지 확인해 보자. 다음 편은 Rust 에서 가장 많이 쓰는 열거형, Option 이다.