패턴
패턴
어떤 값이 특정 구조에 맞는지 확인하고, 그 값 안에 담긴 일부를 변수에 담고 싶을 때가 있어요. Rust의 패턴(pattern) 이 바로 그 일을 해요. 값과 구조를 대조(match)하고, 선택적으로 그 안의 값을 변수에 바인딩하며, 변수 선언과 함수·클로저 파라미터에도 쓰여요.
출처: Rust Reference
본문
구문 (Syntax)
Pattern → |? PatternNoTopAlt ( | PatternNoTopAlt )*
PatternNoTopAlt →
PatternWithoutModernRange
| ModernRangePattern
PatternWithoutModernRange →
LiteralPattern
| IdentifierPattern
| WildcardPattern
| RestPattern
| ReferencePattern
| StructPattern
| TupleStructPattern
| TuplePattern
| GroupedPattern
| SlicePattern
| PathPattern
| MacroInvocation
| ObsoleteRangePattern
패턴의 역할
다음 예시의 패턴은 한 번에 네 가지 일을 해요.
person이car필드에 뭔가 채워져 있는지 검사해요.person의age필드가 13에서 19 사이인지 검사하고, 그 값을person_age변수에 바인딩해요.name필드에 대한 참조를person_name변수에 바인딩해요.person의 나머지 필드를 무시해요. 남은 필드는 어떤 값을 가져도 상관없고 어떤 변수에도 바인딩되지 않아요.
#![allow(unused)]
fn main() {
struct Car;
struct Computer;
struct Person {
name: String,
car: Option<Car>,
computer: Option<Computer>,
age: u8,
}
let person = Person {
name: String::from("John"),
car: Some(Car),
computer: None,
age: 15,
};
if let
Person {
car: Some(_),
age: person_age @ 13..=19,
name: ref person_name,
..
} = person
{
println!("{} has a car and is {} years old.", person_name, person_age);
}
}
패턴은 다음과 같은 곳에서 사용돼요.
let선언- 함수와 클로저 파라미터
match표현식if let표현식while let표현식for표현식
구조 분해 (Destructuring)
패턴은 구조체, 열거형, 튜플을 구조 분해(destructure) 하는 데 쓰여요. 구조 분해는 값을 구성 요소 조각들로 쪼개는 것이고, 그 문법은 값을 만들 때의 문법과 거의 같아요.
스크루티니(scrutinee) 표현식이 구조체·열거형·튜플 타입을 가진 패턴에서는, 와일드카드 패턴(_) 은 하나의 데이터 필드를 대신하고, etc/rest 패턴(..) 은 특정 variant의 남은 필드 전체를 대신해요.
이름 있는(번호가 아닌) 필드를 가진 데이터 구조를 분해할 때는 fieldname을 fieldname: fieldname의 줄임말로 쓸 수 있어요.
#![allow(unused)]
fn main() {
enum Message {
Quit,
WriteString(String),
Move { x: i32, y: i32 },
ChangeColor(u8, u8, u8),
}
let message = Message::Quit;
match message {
Message::Quit => println!("Quit"),
Message::WriteString(write) => println!("{}", &write),
Message::Move{ x, y: 0 } => println!("move {} horizontally", x),
Message::Move{ .. } => println!("other move"),
Message::ChangeColor { 0: red, 1: green, 2: _ } => {
println!("color change, red: {}, green: {}", red, green);
}
};
}
반증 가능성 (Refutability)
패턴은 반증 가능(refutable) 하거나 반증 불가(irrefutable) 해요. 패턴이 대조되는 값에 의해 매칭되지 않을 가능성이 있다면 반증 가능한 패턴이에요. 반대로 항상 매칭되는 패턴은 반증 불가한 패턴이에요.
#![allow(unused)]
fn main() {
let (x, y) = (1, 2); // "(x, y)" is an irrefutable pattern
if let (a, 3) = (1, 2) { // "(a, 3)" is refutable, and will not match
panic!("Shouldn't reach here");
} else if let (a, 4) = (3, 4) { // "(a, 4)" is refutable, and will match
println!("Matched ({}, 4)", a);
}
}
(x, y)는 어떤 튜플에도 매칭되지만, (a, 3)은 두 번째 요소가 정확히 3일 때만 매칭되기 때문에 반증 가능해요.
리터럴 패턴 (Literal patterns)
LiteralPattern → -? LiteralExpression
리터럴 패턴은 그 리터럴이 만들어 내는 값과 정확히 같은 값에 매칭돼요. 음수는 리터럴이 아니기 때문에, 패턴의 리터럴 앞에는 부정 연산자처럼 동작하는 선택적 마이너스 부호를 붙일 수 있어요.
여기서 한 가지 주의할 게 있어요. C 문자열과 raw C 문자열 리터럴은 리터럴 패턴으로 받아들여지지만, &CStr이 구조적 동등성(#[derive(Eq, PartialEq)])을 구현하지 않아서, &CStr에 대한 그런 매칭은 타입 오류로 거부돼요.
리터럴 패턴은 항상 반증 가능해요.
#![allow(unused)]
fn main() {
for i in -2..5 {
match i {
-1 => println!("It's minus one"),
1 => println!("It's a one"),
2|4 => println!("It's either a two or a four"),
_ => println!("Matched none of the arms"),
}
}
}
식별자 패턴 (Identifier patterns)
IdentifierPattern → ref? mut? IDENTIFIER ( @ PatternNoTopAlt )?
식별자 패턴은 매칭된 값을 값 네임스페이스(value namespace) 의 변수에 바인딩해요. 식별자는 패턴 안에서 유일해야 해요. 그리고 이 변수는 스코프에 있는 같은 이름의 변수들을 가려(shadow) 요. 새 바인딩의 스코프는 패턴이 쓰인 문맥(예: let 바인딩이나 match arm)에 따라 달라져요.
mut를 붙일 수 있는 식별자만으로만 된 패턴은 어떤 값에도 매칭되고 그 값을 그 식별자에 바인딩해요. 이건 변수 선언과 함수·클로저 파라미터에서 가장 흔히 쓰이는 패턴이에요.
#![allow(unused)]
fn main() {
let mut variable = 10;
fn sum(x: i32, y: i32) -> i32 {
x + y
}
}
매칭된 값을 변수에 바인딩하려면 variable @ subpattern 문법을 써요. 예를 들어 다음 코드는 e에 값 2를 바인딩해요 (전체 범위가 아니라 — 여기서 범위는 서브패턴일 뿐이에요).
#![allow(unused)]
fn main() {
let x = 2;
match x {
e @ 1 ..= 5 => println!("got a range element {}", e),
_ => println!("anything"),
}
}
기본적으로 식별자 패턴은, 매칭된 값이 Copy를 구현하는지에 따라 그 값을 복사하거나 이동(move) 해서 변수에 바인딩해요. ref 키워드를 쓰면 참조로, ref mut을 쓰면 가변 참조로 바인딩하도록 바꿀 수 있어요.
#![allow(unused)]
fn main() {
let a = Some(10);
match a {
None => (),
Some(value) => (),
}
match a {
None => (),
Some(ref value) => (),
}
}
첫 번째 match에서는 값이 복사(또는 이동)되고, 두 번째 match에서는 같은 메모리 위치에 대한 참조가 value 변수에 바인딩돼요. 이 문법이 필요한 이유는, 구조 분해 서브패턴에서는 & 연산자를 값의 필드에 적용할 수 없기 때문이에요. 예를 들어 다음은 유효하지 않아요.
#![allow(unused)]
fn main() {
struct Person {
name: String,
age: u8,
}
let value = Person { name: String::from("John"), age: 23 };
if let Person { name: &person_name, age: 18..=150 } = value { }
}
이걸 유효하게 만들려면 이렇게 써야 해요.
#![allow(unused)]
fn main() {
struct Person {
name: String,
age: u8,
}
let value = Person { name: String::from("John"), age: 23 };
if let Person { name: ref person_name, age: 18..=150 } = value { }
}
그래서 ref는 매칭의 대상이 아니라, 매칭된 것을 복사·이동하는 대신 바인딩을 참조로 만들기 위한 것뿐이에요.
한 가지 중요한 우선순위가 있어요. 경로 패턴(path pattern)이 식별자 패턴보다 우선해요. 그리고 패턴이 단일 세그먼트 식별자일 때는 그것이 식별자 패턴인지 경로 패턴인지 문법상 모호한데, 이 모호함은 이름 해석(name resolution) 후에야 해결돼요.
#![allow(unused)]
fn main() {
const EXPECTED_VALUE: u8 = 42;
// ^^^^^^^^^^^^^^ That this constant is in scope affects how the
// patterns below are treated.
fn check_value(x: u8) -> Result<u8, u8> {
match x {
EXPECTED_VALUE => Ok(x),
// ^^^^^^^^^^^^^^ Parsed as a `PathPattern` that resolves to
// the constant `42`.
other_value => Err(x),
// ^^^^^^^^^^^ Parsed as an `IdentifierPattern`.
}
}
}
// If `EXPECTED_VALUE` were treated as an `IdentifierPattern` above,
// that pattern would always match, making the function always return
// `Ok(_) regardless of the input.
assert_eq!(check_value(42), Ok(42));
assert_eq!(check_value(43), Err(43));
만약 EXPECTED_VALUE가 위에서 식별자 패턴으로 취급됐다면 그 패턴은 항상 매칭되어, 함수는 입력과 무관하게 항상 Ok(_)를 반환했을 거예요.
ref나 ref mut를 지정했는데 그 식별자가 상수를 가린다면 그것은 오류예요. 식별자 패턴은 @ 서브패턴이 반증 불가하거나 서브패턴이 지정되지 않은 경우 반증 불가해요.
바인딩 모드 (Binding modes)
더 나은 편의성을 위해, 패턴은 참조를 값에 바인딩하기 쉽도록 여러 바인딩 모드(binding mode) 로 동작해요. 참조 값이 비참조 패턴(non-reference pattern) 으로 매칭되면, 자동으로 ref 또는 ref mut 바인딩으로 취급돼요.
#![allow(unused)]
fn main() {
let x: &Option<i32> = &Some(3);
if let Some(y) = x {
// y was converted to `ref y` and its type is &i32
}
}
비참조 패턴에는 바인딩, 와일드카드 패턴(_), 참조 타입의 const 패턴, 참조 패턴을 제외한 모든 패턴이 포함돼요. 바인딩 패턴이 ref, ref mut, mut를 명시하지 않으면 기본 바인딩 모드(default binding mode) 를 사용해 변수가 어떻게 바인딩될지 결정해요.
기본 바인딩 모드는 "이동(move)" 모드에서 시작하며 이동 의미론을 사용해요. 패턴을 매칭할 때 컴파일러는 패턴의 바깥에서 안쪽으로 진행해요. 참조가 비참조 패턴으로 매칭될 때마다 자동으로 역참조하고 기본 바인딩 모드를 갱신해요.
- 참조는 기본 바인딩 모드를
ref로 설정해요. - 가변 참조는 모드를
ref mut으로 설정하는데, 단 이미ref라면ref로 유지돼요. - 자동 역참조된 값이 여전히 참조라면 다시 역참조하고 이 과정을 반복해요.
바인딩 패턴이 ref·ref mut 바인딩 모드 또는 mut로 가변성을 명시할 수 있는 건 기본 바인딩 모드가 "move"일 때뿐이에요. 예를 들어 다음은 받아들여지지 않아요.
#![allow(unused)]
fn main() {
let [mut x] = &[()]; //~ ERROR
let [ref x] = &[()]; //~ ERROR
let [ref mut x] = &mut [()]; //~ ERROR
}
2024 Edition 차이 — 2024 에디션 이전에는 기본 바인딩 모드가 "move"가 아니어도 바인딩이 ref·ref mut 바인딩 모드를 명시할 수 있었고, 그런 바인딩에 mut로 가변성을 지정할 수 있었어요. 이 에디션들에서는 바인딩에 mut를 지정하면 기본 바인딩 모드와 무관하게 바인딩 모드가 "move"로 설정됐어요.
마찬가지로, 참조 패턴은 기본 바인딩 모드가 "move"일 때만 나타날 수 있어요. 다음은 받아들여지지 않아요.
#![allow(unused)]
fn main() {
let [&x] = &[&()]; //~ ERROR
}
2024 Edition 차이 — 2024 에디션 이전에는 기본 바인딩 모드가 "move"가 아니어도 참조 패턴이 나타날 수 있었고, 스크루티니에 대한 매칭과 기본 바인딩 모드를 "move"로 재설정하는 효과를 모두 가졌어요.
이동 바인딩과 참조 바인딩은 같은 패턴에서 섞일 수 있어요. 이렇게 되면 바인딩된 객체의 부분 이동(partial move) 이 발생하고, 그 후로 그 객체는 사용할 수 없게 돼요. 이는 타입을 복사할 수 없는 경우에만 적용돼요.
다음 예시에서 name은 person에서 이동돼요. 부분 이동 때문에 person 전체를 쓰거나 person.name을 쓰려 하면 오류가 나요.
#![allow(unused)]
fn main() {
struct Person {
name: String,
age: u8,
}
let person = Person{ name: String::from("John"), age: 23 };
// `name` is moved from person and `age` referenced
let Person { name, ref age } = person;
}
와일드카드 패턴 (Wildcard pattern)
WildcardPattern → _
와일드카드 패턴(밑줄 기호)은 어떤 값에도 매칭돼요. 값이 중요하지 않을 때 무시하는 데 쓰여요. 다른 패턴 안에서는 하나의 데이터 필드를 매칭해요 (..가 남은 필드를 매칭하는 것과 대조적이에요). 식별자 패턴과 달리, 매칭된 값을 복사·이동·빌려오지 않아요.
#![allow(unused)]
fn main() {
let x = 20;
let (a, _) = (10, x); // the x is always matched by _
assert_eq!(a, 10);
// ignore a function/closure param
let real_part = |a: f64, _: f64| { a };
// ignore a field from a struct
struct RGBA {
r: f32,
g: f32,
b: f32,
a: f32,
}
let color = RGBA{r: 0.4, g: 0.1, b: 0.9, a: 0.5};
let RGBA{r: red, g: green, b: blue, a: _} = color;
assert_eq!(color.r, red);
assert_eq!(color.g, green);
assert_eq!(color.b, blue);
// accept any Some, with any value
let x = Some(10);
if let Some(_) = x {}
}
와일드카드 패턴은 항상 반증 불가해요.
나머지 패턴 (Rest pattern)
RestPattern → ..
나머지 패턴(rest pattern)(.. 토큰)은 가변 길이 패턴으로, 앞뒤에서 아직 매칭되지 않은 0개 이상의 요소에 매칭돼요.
이 패턴은 튜플, 튜플 구조체, 슬라이스 패턴에서만 사용할 수 있고, 그 패턴들의 요소 중 딱 한 번만 나타날 수 있어요. 슬라이스 패턴의 식별자 패턴에서도 허용돼요. 나머지 패턴은 항상 반증 불가해요.
#![allow(unused)]
fn main() {
let words = vec!["a", "b", "c"];
let slice = &words[..];
match slice {
[] => println!("slice is empty"),
[one] => println!("single element {}", one),
[head, tail @ ..] => println!("head={} tail={:?}", head, tail),
}
match slice {
// Ignore everything but the last element, which must be "!".
[.., "!"] => println!("!!!"),
// `start` is a slice of everything except the last element, which must be "z".
[start @ .., "z"] => println!("starts with: {:?}", start),
// `end` is a slice of everything but the first element, which must be "a".
["a", end @ ..] => println!("ends with: {:?}", end),
// 'whole' is the entire slice and `last` is the final element
whole @ [.., last] => println!("the last element of {:?} is {}", whole, last),
rest => println!("{:?}", rest),
}
if let [.., penultimate, _] = slice {
println!("next to last is {}", penultimate);
}
let tuple = (1, 2, 3, 4, 5);
// The rest pattern may also be used in tuple and tuple
// struct patterns.
match tuple {
(1, .., y, z) => println!("y={} z={}", y, z),
(.., 5) => println!("tail must be 5"),
(..) => println!("matches everything else"),
}
}
범위 패턴 (Range patterns)
ModernRangePattern →
RangeExclusivePattern
| RangeInclusivePattern
| RangeFromPattern
| RangeToExclusivePattern
| RangeToInclusivePattern
RangeExclusivePattern →
RangePatternBound .. RangePatternBound
RangeInclusivePattern →
RangePatternBound ..= RangePatternBound
RangeFromPattern →
RangePatternBound ..
RangeToExclusivePattern →
.. RangePatternBound
RangeToInclusivePattern →
..= RangePatternBound
ObsoleteRangePattern →
RangePatternBound ... RangePatternBound
RangePatternBound →
LiteralPattern
| PathExpression
범위 패턴은 그 경계(bound)가 정의하는 범위 안의 스칼라 값에 매칭돼요. 기호(sigil)(.. 또는 ..=)와 한쪽 또는 양쪽의 경계로 구성돼요. 기호 왼쪽의 경계를 하한(lower bound), 오른쪽을 상한(upper bound) 이라고 불러요.
- 배타적 범위 패턴(exclusive range pattern) — 하한부터 상한 직전까지(상한은 제외)의 모든 값에 매칭돼요. 하한,
.., 상한 순으로 써요. 예를 들어'm'..'p'는'm','n','o'에만 매칭되고'p'는 포함하지 않아요. - 포괄적 범위 패턴(inclusive range pattern) — 하한부터 상한 까지(상한 포함)의 모든 값에 매칭돼요. 하한,
..=, 상한 순으로 써요. 예를 들어'm'..='p'는'm','n','o','p'에 매칭돼요. - from 범위 패턴(from range pattern) — 하한 이상의 모든 값에 매칭돼요. 하한 다음에
..을 붙여 써요. 예를 들어1..는 1 이상의 모든 정수(1, 9, 9001, 또는 적당한 크기라면 9007199254740991)에 매칭되지만, 0이나 (부호 있는 정수에서) 음수에는 매칭되지 않아요. - to 배타적 범위 패턴(to exclusive range pattern) — 상한 미만의 모든 값에 매칭돼요.
..다음에 상한을 붙여 써요. 예를 들어..10은 10 미만의 모든 정수(9, 1, 0, 그리고 부호 있는 정수 타입의 모든 음수)에 매칭돼요. - to 포괄적 범위 패턴(to inclusive range pattern) — 상한 이하의 모든 값에 매칭돼요.
..=다음에 상한을 붙여 써요. 예를 들어..=10은 10 이하의 모든 정수(10, 1, 0, 그리고 부호 있는 정수 타입의 모든 음수)에 매칭돼요.
범위 패턴은 비어 있으면 안 돼요 — 자신의 타입이 가질 수 있는 값 집합에서 적어도 하나의 값을 포함해야 해요.
a..=b에서는a ≤ b여야 해요. 예를 들어10..=0은 오류이지만10..=10은 허용돼요.a..b에서는a < b여야 해요. 예를 들어10..0이나10..10은 오류예요...b에서b는 그 타입의 가장 작은 값이면 안 돼요. 예를 들어..-128i8이나..f64::NEG_INFINITY는 오류예요.
경계(bound) 는 다음 중 하나로 써요.
- 문자(char), 바이트, 정수, 또는 부동소수점 리터럴
-다음에 정수 또는 부동소수점 리터럴- 경로(path)
참고로, RangePatternBound에 대해 문법적으로는 이보다 더 많은 것을 받아들이지만, 의미상으로 그 외의 것은 나중에 거부돼요. 경계가 경로로 쓰이면, 매크로 해석 후 그 경로는 char, 정수 타입, 또는 부동소수점 타입의 상수 아이템으로 해석되어야 해요.
범위 패턴은 상·하한의 타입에 매칭되는데, 이 두 타입은 같아야 해요. 경계가 경로라면, 그 경계는 경로가 해석되는 상수의 타입과 값을 가져요. 경계가 리터럴이라면, 대응하는 리터럴 표현식과 같은 타입과 값을 가져요. 경계가 -가 붙은 리터럴이라면, 대응하는 리터럴 표현식과 같은 타입을 갖고 그 값을 부정한 값을 가져요. 그리고 부동소수점 범위 패턴에서 상수는 NaN이면 안 돼요.
#![allow(unused)]
fn main() {
let c = 'f';
let valid_variable = match c {
'a'..='z' => true,
'A'..='Z' => true,
'α'..='ω' => true,
_ => false,
};
let ph = 10;
println!("{}", match ph {
0..7 => "acid",
7 => "neutral",
8..=14 => "base",
_ => unreachable!(),
});
let uint: u32 = 5;
match uint {
0 => "zero!",
1.. => "positive number!",
};
// using paths to constants:
const TROPOSPHERE_MIN : u8 = 6;
const TROPOSPHERE_MAX : u8 = 20;
const STRATOSPHERE_MIN : u8 = TROPOSPHERE_MAX + 1;
const STRATOSPHERE_MAX : u8 = 50;
const MESOSPHERE_MIN : u8 = STRATOSPHERE_MAX + 1;
const MESOSPHERE_MAX : u8 = 85;
let altitude = 70;
println!("{}", match altitude {
TROPOSPHERE_MIN..=TROPOSPHERE_MAX => "troposphere",
STRATOSPHERE_MIN..=STRATOSPHERE_MAX => "stratosphere",
MESOSPHERE_MIN..=MESOSPHERE_MAX => "mesosphere",
_ => "outer space, maybe",
});
pub mod binary {
pub const MEGA : u64 = 1024*1024;
pub const GIGA : u64 = 1024*1024*1024;
}
let n_items = 20_832_425;
let bytes_per_item = 12;
if let size @ binary::MEGA..=binary::GIGA = n_items * bytes_per_item {
println!("It fits and occupies {} bytes", size);
}
trait MaxValue {
const MAX: u64;
}
impl MaxValue for u8 {
const MAX: u64 = (1 << 8) - 1;
}
impl MaxValue for u16 {
const MAX: u64 = (1 << 16) - 1;
}
impl MaxValue for u32 {
const MAX: u64 = (1 << 32) - 1;
}
// using qualified paths:
println!("{}", match 0xfacade {
0 ..= <u8 as MaxValue>::MAX => "fits in a u8",
0 ..= <u16 as MaxValue>::MAX => "fits in a u16",
0 ..= <u32 as MaxValue>::MAX => "fits in a u32",
_ => "too big",
});
}
고정 폭 정수와 char 타입의 범위 패턴은, 그 타입의 가능한 값 전체 집합을 덮을 때 반증 불가해요. 예를 들어 0u8..=255u8은 반증 불가해요. 정수 타입의 값 범위는 최솟값에서 최댓값까지의 닫힌 범위(closed range) 고, char 타입의 값 범위는 정확히 모든 유니코드 스칼라 값(Unicode Scalar Values) 을 포함하는 범위, 즉 '\u{0000}'..='\u{D7FF}'와 '\u{E000}'..='\u{10FFFF}'예요.
RangeFromPattern은 슬라이스 패턴의 서브패턴으로 최상위 패턴에서는 쓸 수 없어요. 예를 들어 [1.., _]는 유효한 패턴이 아니에요.
2021 Edition 차이 — 2021 에디션 이전에는, 하한과 상한을 모두 가진 범위 패턴을 ..= 대신 ...으로도 쓸 수 있었고 의미는 같았어요.
참조 패턴 (Reference patterns)
ReferencePattern → ( & | && ) mut? PatternWithoutModernRange
참조 패턴은 매칭되고 있는 포인터를 역참조하고, 그래서 그것을 빌려와요. 예를 들어 x: &i32에 대한 다음 두 매칭은 동등해요.
#![allow(unused)]
fn main() {
let int_reference = &3;
let a = match *int_reference { 0 => "zero", _ => "some" };
let b = match int_reference { &0 => "zero", _ => "some" };
assert_eq!(a, b);
}
참조 패턴의 문법 생성물에서 이중 참조에 대한 참조를 매칭하려면 토큰 &&를 써야 해요. &&는 그 자체로 하나의 토큰이라 두 개의 & 토큰이 아니기 때문이에요.
mut 키워드를 추가하면 가변 참조를 역참조해요. 이때 가변성은 참조의 가변성과 일치해야 해요. 참조 패턴은 항상 반증 불가해요.
구조체 패턴 (Struct patterns)
StructPattern →
PathInExpression {
StructPatternElements?
}
StructPatternElements →
StructPatternFields ( , | , StructPatternEtCetera )?
| StructPatternEtCetera
StructPatternFields →
StructPatternField ( , StructPatternField )*
StructPatternField →
OuterAttribute*
(
TUPLE_INDEX : Pattern
| IDENTIFIER : Pattern
| ref? mut? IDENTIFIER
)
StructPatternEtCetera → ..
구조체 패턴은 서브패턴이 정의하는 모든 기준을 충족하는 구조체, 열거형, 공용체(union) 값에 매칭돼요. 구조체·열거형·공용체 값을 구조 분해하는 데도 쓰여요.
구조체 패턴에서 필드는 이름, (튜플 구조체의 경우) 인덱스, 또는 ..로 참조되거나 무시돼요.
#![allow(unused)]
fn main() {
struct Point {
x: u32,
y: u32,
}
let s = Point {x: 1, y: 1};
match s {
Point {x: 10, y: 20} => (),
Point {y: 10, x: 20} => (), // order doesn't matter
Point {x: 10, ..} => (),
Point {..} => (),
}
struct PointTuple (
u32,
u32,
);
let t = PointTuple(1, 2);
match t {
PointTuple {0: 10, 1: 20} => (),
PointTuple {1: 10, 0: 20} => (), // order doesn't matter
PointTuple {0: 10, ..} => (),
PointTuple {..} => (),
}
enum Message {
Quit,
Move { x: i32, y: i32 },
}
let m = Message::Quit;
match m {
Message::Quit => (),
Message::Move {x: 10, y: 20} => (),
Message::Move {..} => (),
}
}
..를 쓰지 않는다면, 구조체를 매칭하는 구조체 패턴은 모든 필드를 명시해야 해요.
#![allow(unused)]
fn main() {
struct Struct {
a: i32,
b: char,
c: bool,
}
let mut struct_value = Struct{a: 10, b: 'X', c: false};
match struct_value {
Struct{a: 10, b: 'X', c: false} => (),
Struct{a: 10, b: 'X', ref c} => (),
Struct{a: 10, b: 'X', ref mut c} => (),
Struct{a: 10, b: 'X', c: _} => (),
Struct{a: _, b: _, c: _} => (),
}
}
반면 공용체(union)를 매칭하는 구조체 패턴은 정확히 하나의 필드만 명시해야 해요 (공용체에 대한 패턴 매칭 참조).
IDENTIFIER 문법은 어떤 값에도 매칭되고 그것을 주어진 필드와 같은 이름의 변수에 바인딩해요. fieldname: fieldname의 줄임말이에요. ref와 mut 한정자도 식별자 패턴에서 설명한 동작과 함께 쓸 수 있어요.
#![allow(unused)]
fn main() {
struct Struct {
a: i32,
b: char,
c: bool,
}
let struct_value = Struct{a: 10, b: 'X', c: false};
let Struct { a, b, c } = struct_value;
}
구조체 패턴은 PathInExpression이 variant가 둘 이상인 열거형의 생성자로 해석되거나, 서브패턴 중 하나가 반증 가능하면 반증 가능해요. 구조체 패턴은 타입 네임스페이스에서 PathInExpression의 생성자가 해석되는 구조체·공용체·열거형 variant에 매칭돼요.
튜플 구조체 패턴 (Tuple struct patterns)
TupleStructPattern → PathInExpression ( TupleStructItems? )
TupleStructItems → Pattern ( , Pattern )* ,?
튜플 구조체 패턴은 서브패턴이 정의하는 모든 기준을 충족하는 튜플 구조체와 열거형 값에 매칭돼요. 튜플 구조체나 열거형 값을 구조 분해하는 데도 쓰여요.
튜플 구조체 패턴은 PathInExpression이 variant가 둘 이상인 열거형의 생성자로 해석되거나, 서브패턴 중 하나가 반증 가능하면 반증 가능해요. 그리고 값 네임스페이스에서 PathInExpression의 생성자가 해석되는 튜플 구조체 또는 튜플형 열거형 variant에 매칭돼요.
반대로, 튜플 구조체나 튜플형 열거형 variant에 대한 구조체 패턴(예: S { 0: _ })은 타입 네임스페이스에서 생성자가 해석되는 튜플 구조체나 variant에 매칭돼요.
enum E1 { V(u16) }
enum E2 { V(u32) }
// Import `E1::V` from the type namespace only.
mod _0 {
const V: () = (); // For namespace masking.
pub(super) use super::E1::*;
}
use _0::*;
// Import `E2::V` from the value namespace only.
mod _1 {
struct V {} // For namespace masking.
pub(super) use super::E2::*;
}
use _1::*;
fn f() {
// This struct pattern matches against the tuple-like
// enum variant whose constructor was found in the type
// namespace.
let V { 0: ..=u16::MAX } = (loop {}) else { loop {} };
// This tuple struct pattern matches against the tuple-like
// enum variant whose constructor was found in the value
// namespace.
let V(..=u32::MAX) = (loop {}) else { loop {} };
}
// Required due to the odd behavior of `super` within functions.
fn main() {}
Lang 팀은 PR #138458에서처럼 패턴에 대해 이런 방식으로 값 네임스페이스를 쓰는 것이 바람직한지에 의문을 제기하는 결정을 내린 적이 있어요 (PR #140593 참조). 코드에서 이 미묘한 점에 의도적으로 의존하지 않는 편이 신중해요.
튜플 패턴 (Tuple patterns)
TuplePattern → ( TuplePatternItems? )
TuplePatternItems →
Pattern ,
| RestPattern
| Pattern ( , Pattern )+ ,?
튜플 패턴은 서브패턴이 정의하는 모든 기준을 충족하는 튜플 값에 매칭돼요. 튜플을 구조 분해하는 데도 쓰여요. 단일 RestPattern을 가진 (..) 형태는 특별한 형태로, 쉼표가 필요 없고 어떤 크기의 튜플에도 매칭돼요.
튜플 패턴은 서브패턴 중 하나가 반증 가능하면 반증 가능해요.
#![allow(unused)]
fn main() {
let pair = (10, "ten");
let (a, b) = pair;
assert_eq!(a, 10);
assert_eq!(b, "ten");
}
그룹화된 패턴 (Grouped patterns)
GroupedPattern → ( Pattern )
패턴을 괄호로 감싸는 것으로 복합 패턴의 우선순위를 명시적으로 제어할 수 있어요. 예를 들어 참조 패턴과 범위 패턴이 나란히 있는 &0..=5 같은 것은 모호해서 허용되지 않지만, 괄호로 감싸서 표현할 수 있어요.
#![allow(unused)]
fn main() {
let int_reference = &3;
match int_reference {
&(0..=5) => (),
_ => (),
}
}
슬라이스 패턴 (Slice patterns)
SlicePattern → [ SlicePatternItems? ]
SlicePatternItems → Pattern ( , Pattern )* ,?
슬라이스 패턴은 고정 크기의 배열과 동적 크기의 슬라이스 모두에 매칭될 수 있어요.
#![allow(unused)]
fn main() {
// Fixed size
let arr = [1, 2, 3];
match arr {
[1, _, _] => "starts with one",
[a, b, c] => "starts with something else",
};
}
#![allow(unused)]
fn main() {
// Dynamic size
let v = vec![1, 2, 3];
match v[..] {
[a, b] => { /* this arm will not apply because the length doesn't match */ }
[a, b, c] => { /* this arm will apply */ }
_ => { /* this wildcard is required, since the length is not known statically */ }
};
}
배열을 매칭할 때 슬라이스 패턴은 각 요소가 반증 불가하다면 반증 불가해요. 슬라이스를 매칭할 때는 단일 .. rest 패턴 또는 .. rest 패턴을 서브패턴으로 가진 식별자 패턴 형태에서만 반증 불가해요.
슬라이스 안에서, 하한과 상한을 모두 갖지 않은 범위 패턴은 (a..)처럼 괄호로 감싸야 해요. 이건 단일 슬라이스 요소에 대한 매칭이라는 의도를 명확히 하기 위해서예요. a..=b처럼 하한과 상한을 모두 가진 범위 패턴은 괄호로 감쌀 필요가 없어요.
경로 패턴 (Path patterns)
PathPattern → PathExpression
경로 패턴은 상수 값 또는 필드가 없는 구조체나 열거형 variant를 가리키는 패턴이에요.
한정되지 않은(unqualified) 경로 패턴은 다음을 가리킬 수 있어요.
- 열거형 variant
- 구조체
- 상수
- 연관 상수(associated constant)
한정된(qualified) 경로 패턴은 연관 상수만 가리킬 수 있어요.
경로 패턴은 구조체, 또는 variant가 하나뿐인 열거형의 variant, 또는 반증 불가한 타입의 상수를 가리키면 반증 불가해요. 반증 가능한 상수나 variant가 여러 개인 열거형의 variant를 가리키면 반증 가능해요.
상수 패턴 (Constant patterns)
타입 T의 상수 C가 패턴으로 쓰일 때, 먼저 T: PartialEq인지 확인해요. 그리고 C의 값이 (재귀적) 구조적 동등성(structural equality) 을 가져야 해요. 이는 다음과 같이 재귀적으로 정의돼요.
- 정수와
str,bool,char값은 항상 구조적 동등성을 가져요. - 튜플, 배열, 슬라이스는 모든 필드/요소가 구조적 동등성을 가지면 구조적 동등성을 가져요 (특히
()와[]는 항상 그래요). - 참조는 가리키는 값이 구조적 동등성을 가지면 구조적 동등성을 가져요.
- 구조체나 열거형 타입의 값은 그
PartialEq인스턴스가#[derive(PartialEq)]로 파생되었고, 모든 필드(열거형이면 활성 variant의 필드)가 구조적 동등성을 가지면 구조적 동등성을 가져요. - 원시 포인터는 상수 정수로 정의되었다면(그리고 캐스트·트랜스뮤트되었다면) 구조적 동등성을 가져요.
- 부동소수점 값은 NaN이 아니면 구조적 동등성을 가져요.
- 그 외의 것은 구조적 동등성을 가지지 않아요.
특히, C의 값은 패턴 빌드 시점(pre-monomorphization) 에 알려져 있어야 해요. 그래서 제네릭 파라미터를 포함하는 연관 const는 패턴으로 쓸 수 없어요. 또한 C의 값은 가변 static(static mut 아이템이나 내부 가변 static 아이템)이나 extern static에 대한 참조를 포함해서는 안 돼요.
모든 조건을 충족한 후에, 상수 값은 패턴으로 번역되고, 이제 그 패턴이 직접 쓰인 것처럼 정확히 동작해요. 특히 완전성(exhaustiveness) 검사에 완전히 참여해요. (원시 포인터의 경우, 상수가 그런 패턴을 쓰는 유일한 방법이에요. 이 타입에서는 오직 _만 항상 완전한 것으로 간주돼요.)
OR 패턴 (Or-patterns)
OR 패턴(Or-patterns) 은 두 개 이상의 서브패턴 중 하나에 매칭되는 패턴이에요 (예: A | B | C). 임의로 중첩될 수 있어요. 문법적으로 OR 패턴은 다른 패턴이 허용되는 어느 곳에서나(Pattern 생성물로 표현) 허용되지만, let 바인딩과 함수·클로저 파라미터(PatternNoTopAlt 생성물로 표현)는 예외예요.
정적 의미론 (Static semantics)
어떤 임의의 패턴 p와 q에 대해, 어떤 깊이의 패턴 p | q가 주어졌을 때 다음 중 하나라도 해당하면 그 패턴은 잘못된 형태(ill-formed) 로 간주돼요.
p에 대해 유추된 타입이q에 대해 유추된 타입과 통일(unify)되지 않는다.p와q에 같은 변수 묶음(bindings)이 도입되지 않는다.p와q에서 같은 이름을 가진 두 바인딩의 타입이 타입이나 바인딩 모드에 관해 통일되지 않는다.
타입의 통일은 모든 경우에서 정확하며, 암묵적 타입 변환은 적용되지 않아요.
match e_s { a_1 => e_1, ... a_n => e_n } 표현식을 타입 검사할 때, p_i | q_i 형태의 패턴을 포함하는 각 match arm a_i에 대해, p_i | q_i가 존재하는 깊이 d에서 e_s의 조각(fragment) 타입이 p_i | q_i와 통일되지 않으면 그 패턴은 잘못된 형태로 간주돼요.
완전성 검사와 관련해서, 패턴 p | q는 p뿐 아니라 q도 덮는 것으로 간주돼요. 어떤 생성자 c(x, ..)에 대해 분배 법칙이 적용되어, c(p | q, ..rest)는 c(p, ..rest) | c(q, ..rest)와 같은 값 집합을 덮어요. 이는 최상위에 존재하는 것 외에 p | q 형태의 중첩 패턴이 더 없을 때까지 재귀적으로 적용될 수 있어요.
여기서 "생성자(constructor)"는 튜플 구조체 패턴이 아니라 곱 타입(product type)에 대한 패턴을 가리켜요. 여기에는 열거형 variant, 튜플 구조체, 이름 있는 필드의 구조체, 배열, 튜플, 슬라이스가 포함돼요.
동적 의미론 (Dynamic semantics)
어떤 생성자 c와 임의의 패턴 p, q, 그리고 선택적으로 남은 요소 rest에 대해, 깊이 d에서 스크루티니 e_s를 패턴 c(p | q, ..rest)와 매칭하는 동적 의미론은 c(p, ..rest) | c(q, ..rest)와 매칭하는 것과 같은 것으로 정의돼요.
다른 비구분(undelimited) 패턴과의 우선순위 (Precedence with other undelimited patterns)
이 장의 다른 곳에서 보았듯, 문법적으로 비구분(undelimited) 인 패턴이 여러 종류 있어요 — 식별자 패턴, 참조 패턴, OR 패턴이 그것이에요. 이 중 OR 패턴은 항상 가장 낮은 우선순위를 가져요. 이는 미래의 타입 지정(type ascription) 기능을 위해 문법적 공간을 예약하고 애매함을 줄이기 위해서예요. 예를 들어 x @ A(..) | B(..)는 x가 모든 패턴에 바인딩되지 않았다는 오류를 내고, &A(x) | B(x)는 서로 다른 서브패턴의 x 사이에 타입 불일치를 만들어 내요.
ObsoleteRangePattern 문법은 2021 에디션 이후로는 의미상 유효하지 않아요.
더 알아보기 (Learn more)
- Rust Reference - Patterns — 본문 원문
- Rust Reference - Match expressions — 패턴 매칭을 실제로 쓰는 표현식
- Rust Reference - if let / while let — let 표현식과 패턴의 조합