`if let`을 사용한 간결한 제어 흐름
실패할 수 있는 패턴 하나를 간결하게 처리하는 if let의 동작, 바인딩 범위, else의 합류 의미와 match로 전환할 기준을 익힙니다.
if let은 값 하나를 패턴에 대조하고, 그 패턴이 맞을 때만 코드를 실행하는 표현식입니다.
Some만을 위한 문법은 아닙니다. 열거형 배리언트뿐 아니라 리터럴, 범위, 튜플, 구조체, 중첩 패턴과 |로 묶은 or-패턴 등 일반적인 패턴을 사용할 수 있습니다. 보통은 값에 따라 성공하거나 실패할 수 있는(refutable) 패턴을 넣습니다.
한 패턴이 맞을 때만 실행하기
다음 match는 config_max가 Some일 때만 내부 값을 출력합니다.
Some일 때만 코드를 실행하는 match
let config_max = Some(3u8);
match config_max {
Some(max) => println!("The maximum is configured to be {max}"),
_ => (),
}관심 있는 패턴이 하나뿐이라면 같은 동작을 if let으로 줄일 수 있습니다.
let config_max = Some(3u8);
if let Some(max) = config_max {
println!("The maximum is configured to be {max}");
}= 왼쪽의 Some(max)가 패턴이고 오른쪽의 config_max가 대조할 값입니다. 패턴이 맞으면 내부 값이 max에 바인딩되고 본문이 실행됩니다. 맞지 않으면 본문을 건너뜁니다.
위와 같은 기본형에서 max의 범위는 패턴이 성립한 본문 블록입니다. else 블록이나 if let 표현식 뒤에서는 max를 사용할 수 없습니다. Rust 2024 에디션의 let 체인에서는 앞 패턴의 바인딩을 뒤 조건과 최종 본문에서도 사용할 수 있지만, 이 교재가 기준으로 삼는 2021 에디션에서는 중첩 if let으로 같은 흐름을 표현합니다. 어느 에디션에서도 불일치 경로에는 그 패턴의 바인딩이 생기지 않습니다.
if let P = value
패턴 일치
본문을 실행하고 P가 만든 바인딩을 그 본문 안에서 사용합니다.
패턴 불일치
바인딩은 생기지 않습니다. 모든 불일치 값은 하나의 else로 가거나, else가 없으면 건너뜁니다.
| 판단 축 | if let |
match |
|---|---|---|
| 관심 범위 | 한 패턴 또는 한 or-패턴이 중요함 | 여러 경우가 서로 다른 의미를 가짐 |
| 나머지 값 | 무시하거나 하나의 else로 합침 |
모든 가능성을 갈래로 덮음 |
| 전환 신호 | 짧은 부수 효과 또는 단순한 두 경로 | 갈래별 결과·매치 가드·의미 있는 경우가 늘어남 |
| 변경 감지 | 불일치 값의 종류를 검사하지 않음 | 배리언트를 명시하면 추가 시 누락이 오류가 됨 |
if let유지- 한 패턴 또는 or-패턴만 중요하고, 나머지는 무시하거나 하나의
else로 처리합니다. match로 전환- 경우별 결과, 매치 가드, 서로 다른 의미가 늘어나 모든 갈래를 한눈에 검토해야 합니다.
match의 _도 철저성을 만족하지만 미래 배리언트까지 기존 기본 갈래에 넣습니다. 새 배리언트마다 의미를 재검토해야 한다면 닫힌 열거형의 현재 배리언트를 명시적으로 나열합니다.
if let은 안전하지 않은 축약이 아니라 불일치 값을 의도적으로 한 경로로 접는 표현식입니다. 그 접힘이 더 이상 도메인 의미와 맞지 않을 때 match로 펼칩니다.or-패턴도 사용할 수 있습니다. 각 대안이 바인딩을 만든다면 이름, 타입, 바인딩 방식이 서로 같아야 합니다.
let status = Some(201u16);
if let Some(code @ (200 | 201)) = status {
println!("accepted: {code}");
}이 예제의 패턴은 Some(200) 또는 Some(201)에만 맞고, 맞은 값을 code에 바인딩합니다. 그 밖의 Some 값과 None은 모두 불일치입니다.
if let은 대략 “첫 갈래만 쓰고 나머지는 _로 처리하는 match”에 해당하는 문법 설탕입니다. 코드가 짧아지는 대신, match가 제공하는 모든 경우 검사를 포기하므로 불일치 값을 정말 무시해도 되는지 설계자가 판단해야 합니다.
else는 모든 불일치 값을 한 경로로 모읍니다
if let에는 else를 붙일 수 있습니다. else는 특정한 두 번째 패턴이 아니라, 앞의 패턴과 맞지 않은 모든 값을 한꺼번에 처리합니다.
쿼터 동전이면 주 이름을 출력하고, 쿼터가 아닌 모든 동전은 개수만 센다고 해봅시다. match로는 다음과 같이 작성할 수 있습니다.
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {state:?}!"),
_ => count += 1,
}두 경로만 구분하면 되므로 if let ... else로 같은 뜻을 표현할 수 있습니다.
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {state:?}!");
} else {
count += 1;
}state는 Coin::Quarter(state)가 맞은 본문에서만 존재합니다. else에는 Penny, Nickel, Dime처럼 패턴과 맞지 않은 값이 모두 들어오지만, 어떤 배리언트였는지나 그 내부 데이터는 새 패턴으로 분해되지 않습니다.
if let ... else도 표현식이므로 두 블록에서 같은 타입의 값을 만들 수 있습니다. 다만 여러 배리언트가 서로 다른 값을 만들거나, 갈래마다 별도의 조건을 붙이기 시작하면 match가 분기와 결과를 더 명확하게 드러냅니다.
match로 전환할 기준
다음과 같은 경우에는 간결함보다 match의 구조가 더 중요합니다.
- 여러 경우가 각각 다른 의미와 동작을 가질 때
- 갈래별 반환값으로 하나의 값을 만들 때
- 패턴마다 매치 가드를 붙여 조건을 더 좁힐 때
- 열거형의 모든 배리언트를 의식적으로 검토하고 누락을 컴파일 오류로 만들고 싶을 때
match는 모든 가능성을 덮어야 하지만, _도 모든 나머지 값을 덮는 유효한 포괄 패턴입니다. 따라서 닫힌 열거형에서 기존 배리언트를 _로 묶어 두면 나중에 새 배리언트가 추가되어도 그 값이 기존 포괄 갈래로 들어가며, “새 의미를 별도로 처리해야 하는가?”를 컴파일러가 다시 묻지 못할 수 있습니다.
정말 같은 기본 동작이 맞을 때만 _를 사용하세요. 새 배리언트마다 동작을 재검토해야 한다면 현재 배리언트를 명시적으로 나열하는 편이 낫습니다. 반대로 다른 크레이트가 공개한 #[non_exhaustive] 열거형은 미래 배리언트를 허용하므로 외부에서 매칭할 때 포괄 갈래가 필요합니다. 이때는 포괄 경로가 무엇을 보장하는지 코드와 테스트로 분명히 남겨야 합니다.
Option<T>에서 Some만 부수 효과로 처리하고 None은 의도적으로 무시한다면 if let이 잘 맞습니다. Some과 None이 각각 결과를 만들거나, 경우별 조건과 처리가 늘어난다면 match를 선택하세요.
지금까지 구조체와 열거형으로 도메인 값을 표현하고, Option<T>로 값의 부재를 타입에 담고, match와 if let으로 그 값을 안전하게 분해하는 방법을 살펴봤습니다. 다음 장에서는 잘 조직된 API를 만들기 위한 러스트의 모듈을 다룹니다.