홈강좌Rust 입문·중급 47편 중 26편째
Rust Option — 값이 없을 수도 있다는 것을 타입으로 적는다
Rust 에는 null 이 없다. 대신 값이 있으면 Some(값), 없으면 None 인 열거형 Option 을 쓴다. Option<i32> 는 i32 가 아니라서 그대로 쓰면 E0308 이 난다. match 나 unwrap_or 로 꺼내 쓴다. unwrap 은 None 이면 패닉하니 확신할 때만 쓴다.
01 있으면 Some, 없으면 None
배열에서 점수를 찾아 그 번호를 돌려주는 함수다. 못 찾을 수도 있으니 반환 타입이 Option<usize> 다.
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));
} Compiling find v0.1.0 (~/projects/find)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.17s
Running `target/debug/find`
Some(2)
NoneOption<usize>는 "usize가 있거나, 없거나" 다. 꺾쇠 안이 담길 값의 타입이다.- 찾으면
Some(i)로 번호를 감싸 돌려주고(11편의return으로 일찍 끝낸다), 끝까지 못 찾으면None을 돌려준다. - 매개변수
scores: [i32; 4]는 "i32 4개짜리 배열" 이라는 타입이다(9편).
Option 은 24편의 열거형이다. 표준 라이브러리에 Some(값) 과 None 두 경우로 이미 정의되어 있어서, Option:: 을 붙이지 않고 바로 쓴다.
02 Option 은 값 그대로 쓸 수 없다
Some(5) 를 정수를 받는 함수에 바로 넘겨 보자.
fn double(n: i32) -> i32 {
n * 2
}
fn main() {
let maybe: Option<i32> = Some(5);
println!("{}", double(maybe));
}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")));
| +++++++++++++++++- 무엇 — 타입이 안 맞는다.
i32가 와야 하는데Option<i32>가 왔다. - 어디 — 7번째 줄
double(maybe)의maybe.note:가 함수 정의를 함께 보여 준다. - 고치는 법 —
help:가.expect("REASON")으로 꺼내라고 한다.None이면 패닉하는 방법이다(아래).
이 오류가 Option 의 목적이다. 값이 없을 수도 있는데 있는 것처럼 쓰면 컴파일러가 막는다. 다른 언어에서 null 을 그냥 썼다가 실행 중에 터지는 일을, Rust 는 컴파일할 때 잡는다.
03 match 로 꺼낸다
Option 은 열거형이니 25편의 match 로 두 경우를 나눈다.
fn describe(age: Option<u8>) {
match age {
Some(n) => println!("age is {n}"),
None => println!("age unknown"),
}
}
fn main() {
describe(Some(30));
describe(None);
} 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 unknownSome(n) 갈래에서 안의 값을 n 으로 꺼낸다. None 갈래를 빠뜨리면 25편의 E0004 가 난다. 값이 없는 경우를 잊을 수 없다.
04 실습 — unwrap_or 와 unwrap
매번 match 를 쓰기 번거로우면 메서드로 꺼낸다.
fn main() {
let known: Option<i32> = Some(5);
let missing: Option<i32> = None;
println!("{}", known.unwrap_or(0));
println!("{}", missing.unwrap_or(0));
} Compiling find v0.1.0 (~/projects/find)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.16s
Running `target/debug/find`
5
0unwrap_or(0) 은 Some 이면 안의 값, None 이면 괄호 안의 기본값을 돌려준다. 이번에는 unwrap() 을 None 에 써 보자.
fn main() {
let missing: Option<i32> = None;
let value = missing.unwrap();
println!("{value}");
} 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 backtraceunwrap() 은 Some 이면 값을 꺼내고, None 이면 둘째 줄처럼 패닉으로 멈춘다(None 값에 unwrap() 을 불렀다). 위 help 의 expect("REASON") 도 같은데, 멈출 때 괄호 안의 이유를 함께 찍는다. 둘 다 "절대 None 일 리 없다" 고 확신할 때만 쓴다. 다음 편에서는 match 보다 짧게 쓰는 if let 과 let else 를 본다.