hyunsu blog

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

Rust Option — 값이 없을 수도 있다는 것을 타입으로 적는다

목차 · 4
  1. 01있으면 Some, 없으면 None
  2. 02Option 은 값 그대로 쓸 수 없다
  3. 03match 로 꺼낸다
  4. 04실습 — unwrap_or 와 unwrap

Rust 에는 null 이 없다. 대신 값이 있으면 Some(값), 없으면 None 인 열거형 Option 을 쓴다. Option<i32> 는 i32 가 아니라서 그대로 쓰면 E0308 이 난다. match 나 unwrap_or 로 꺼내 쓴다. unwrap 은 None 이면 패닉하니 확신할 때만 쓴다.

01 있으면 Some, 없으면 None

배열에서 점수를 찾아 그 번호를 돌려주는 함수다. 못 찾을 수도 있으니 반환 타입이 Option<usize> 다.

rust
fn find_index(scores: [i32; 4], target: i32) -> Option<usize> {
    for i in 0..scores.len() {
        if scores[i] == target {
            return Some(i);
        }
    }
    None
}

fn main() {
    let scores = [72, 85, 90, 64];
    println!("{:?}", find_index(scores, 90));
    println!("{:?}", find_index(scores, 100));
}
text
   Compiling find v0.1.0 (~/projects/find)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
     Running `target/debug/find`
Some(2)
None
  • Option<usize> 는 "usize 가 있거나, 없거나" 다. 꺾쇠 안이 담길 값의 타입이다.
  • 찾으면 Some(i) 로 번호를 감싸 돌려주고(11편의 return 으로 일찍 끝낸다), 끝까지 못 찾으면 None 을 돌려준다.
  • 매개변수 scores: [i32; 4] 는 "i32 4개짜리 배열" 이라는 타입이다(9편).

Option 은 24편의 열거형이다. 표준 라이브러리에 Some(값) 과 None 두 경우로 이미 정의되어 있어서, Option:: 을 붙이지 않고 바로 쓴다.

02 Option 은 값 그대로 쓸 수 없다

Some(5) 를 정수를 받는 함수에 바로 넘겨 보자.

rust
fn double(n: i32) -> i32 {
    n * 2
}

fn main() {
    let maybe: Option<i32> = Some(5);
    println!("{}", double(maybe));
}
text
error[E0308]: mismatched types
 --> src/main.rs:7:27
  |
7 |     println!("{}", double(maybe));
  |                    ------ ^^^^^ expected `i32`, found `Option<i32>`
  |                    |
  |                    arguments to this function are incorrect
  |
  = note: expected type `i32`
             found enum `Option<i32>`
note: function defined here
 --> src/main.rs:1:4
  |
1 | fn double(n: i32) -> i32 {
  |    ^^^^^^ ------
help: consider using `Option::expect` to unwrap the `Option<i32>` value, panicking if the value is an `Option::None`
  |
7 |     println!("{}", double(maybe.expect("REASON")));
  |                                +++++++++++++++++
  1. 무엇 — 타입이 안 맞는다. i32 가 와야 하는데 Option<i32> 가 왔다.
  2. 어디 — 7번째 줄 double(maybe) 의 maybe. note: 가 함수 정의를 함께 보여 준다.
  3. 고치는 법 — help: 가 .expect("REASON") 으로 꺼내라고 한다. None 이면 패닉하는 방법이다(아래).

이 오류가 Option 의 목적이다. 값이 없을 수도 있는데 있는 것처럼 쓰면 컴파일러가 막는다. 다른 언어에서 null 을 그냥 썼다가 실행 중에 터지는 일을, Rust 는 컴파일할 때 잡는다.

03 match 로 꺼낸다

Option 은 열거형이니 25편의 match 로 두 경우를 나눈다.

rust
fn describe(age: Option<u8>) {
    match age {
        Some(n) => println!("age is {n}"),
        None => println!("age unknown"),
    }
}

fn main() {
    describe(Some(30));
    describe(None);
}
text
   Compiling find v0.1.0 (~/projects/find)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.15s
     Running `target/debug/find`
age is 30
age unknown

Some(n) 갈래에서 안의 값을 n 으로 꺼낸다. None 갈래를 빠뜨리면 25편의 E0004 가 난다. 값이 없는 경우를 잊을 수 없다.

04 실습 — unwrap_or 와 unwrap

매번 match 를 쓰기 번거로우면 메서드로 꺼낸다.

rust
fn main() {
    let known: Option<i32> = Some(5);
    let missing: Option<i32> = None;

    println!("{}", known.unwrap_or(0));
    println!("{}", missing.unwrap_or(0));
}
text
   Compiling find v0.1.0 (~/projects/find)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/find`
5
0

unwrap_or(0) 은 Some 이면 안의 값, None 이면 괄호 안의 기본값을 돌려준다. 이번에는 unwrap() 을 None 에 써 보자.

rust
fn main() {
    let missing: Option<i32> = None;
    let value = missing.unwrap();
    println!("{value}");
}
text
   Compiling find v0.1.0 (~/projects/find)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
     Running `target/debug/find`

thread 'main' (213069211) panicked at src/main.rs:3:25:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

unwrap() 은 Some 이면 값을 꺼내고, None 이면 둘째 줄처럼 패닉으로 멈춘다(None 값에 unwrap() 을 불렀다). 위 help 의 expect("REASON") 도 같은데, 멈출 때 괄호 안의 이유를 함께 찍는다. 둘 다 "절대 None 일 리 없다" 고 확신할 때만 쓴다. 다음 편에서는 match 보다 짧게 쓰는 if let 과 let else 를 본다.