3.3 패턴 매칭
match 표현식
match는 Rust에서 가장 강력한 제어 흐름 구조입니다. TypeScript의 switch와 유사하지만 훨씬 강력합니다.
#![allow(unused)]
fn main() {
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: &Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
}
TypeScript switch와 비교:
function valueInCents(coin: Coin): number {
switch (coin) {
case Coin.Penny: return 1;
case Coin.Nickel: return 5;
case Coin.Dime: return 10;
case Coin.Quarter: return 25;
// default 없어도 TypeScript가 exhaustive check (enum의 경우)
}
}
match의 핵심 규칙: 모든 경우를 처리해야 한다 (exhaustive)
fn value_in_cents(coin: &Coin) -> u32 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
// Dime, Quarter 누락!
}
// error[E0004]: non-exhaustive patterns: `&Coin::Dime` and `&Coin::Quarter` not covered
}
match 팔(arm)의 구조
각 match 팔은 패턴 => 표현식 형태입니다:
fn describe_number(n: i32) -> &'static str {
match n {
// 단일 값
0 => "zero",
// 여러 값 (OR 패턴)
1 | 2 | 3 => "small positive",
// 범위
4..=10 => "medium positive",
// 조건 (match guard)
n if n > 0 => "large positive",
// 나머지 (와일드카드)
_ => "negative",
}
}
fn main() {
println!("{}", describe_number(0)); // zero
println!("{}", describe_number(2)); // small positive
println!("{}", describe_number(7)); // medium positive
println!("{}", describe_number(100)); // large positive
println!("{}", describe_number(-5)); // negative
}
여러 줄 팔
fn process_block(block: &Block) -> String {
match block.status {
BlockStatus::Pending => {
println!("Block is being processed...");
let hash = compute_hash(block);
format!("Pending block hash: {}", hash)
}
BlockStatus::Confirmed => {
format!("Confirmed at height {}", block.height)
}
BlockStatus::Invalid(ref reason) => {
eprintln!("Invalid block: {}", reason);
String::from("invalid")
}
}
}
데이터를 가진 열거형 패턴 매칭
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
fn process(msg: Message) {
match msg {
Message::Quit => {
println!("Quit!");
}
Message::Move { x, y } => {
// 구조 분해로 필드를 직접 꺼냄
println!("Move to ({}, {})", x, y);
}
Message::Write(text) => {
// 튜플 배리언트의 값을 꺼냄
println!("Write: {}", text);
}
Message::ChangeColor(r, g, b) => {
println!("Color: rgb({}, {}, {})", r, g, b);
}
}
}
fn main() {
process(Message::Move { x: 10, y: 20 });
process(Message::Write(String::from("hello")));
process(Message::ChangeColor(255, 128, 0));
process(Message::Quit);
}
구조 분해 (Destructuring)
패턴 매칭의 핵심 기능 중 하나는 구조 분해입니다.
구조체 구조 분해
struct Point {
x: f64,
y: f64,
}
fn main() {
let p = Point { x: 3.0, y: 4.0 };
// 구조 분해로 필드를 변수로 꺼냄
let Point { x, y } = p;
println!("x: {}, y: {}", x, y);
// 다른 이름으로 꺼냄
let Point { x: px, y: py } = Point { x: 1.0, y: 2.0 };
println!("px: {}, py: {}", px, py);
// 일부 필드만 (나머지는 무시)
let Point { x, .. } = Point { x: 5.0, y: 6.0 };
println!("x only: {}", x);
// match에서 구조 분해
let points = vec![
Point { x: 0.0, y: 0.0 },
Point { x: 1.0, y: 5.0 },
];
for point in &points {
match point {
Point { x: 0.0, y: 0.0 } => println!("Origin"),
Point { x, y: 0.0 } => println!("On x-axis at {}", x),
Point { x: 0.0, y } => println!("On y-axis at {}", y),
Point { x, y } => println!("At ({}, {})", x, y),
}
}
}
튜플 구조 분해
fn main() {
let (a, b, c) = (1, 2, 3);
println!("{} {} {}", a, b, c);
// 일부 무시
let (first, _, last) = (1, 2, 3);
println!("{} {}", first, last);
// 중첩 구조 분해
let ((x1, y1), (x2, y2)) = ((1, 2), (3, 4));
println!("({},{}) to ({},{})", x1, y1, x2, y2);
}
열거형 구조 분해
#![allow(unused)]
fn main() {
#[derive(Debug)]
enum TransactionResult {
Success { txid: String, block_height: u64 },
Failure { code: u32, reason: String },
Pending(String), // tx hash
}
fn handle_result(result: TransactionResult) {
match result {
TransactionResult::Success { txid, block_height } => {
println!("TX {} confirmed at block {}", txid, block_height);
}
TransactionResult::Failure { code, reason } => {
println!("TX failed ({}): {}", code, reason);
}
TransactionResult::Pending(hash) => {
println!("TX {} is pending...", hash);
}
}
}
}
와일드카드와 변수 바인딩
fn main() {
let num = 7u32;
match num {
// 값 무시
_ => println!("anything"),
}
// 변수 바인딩과 와일드카드
match num {
n @ 1..=10 => println!("Got {} (1-10)", n), // @ 바인딩
n @ 11..=20 => println!("Got {} (11-20)", n),
_ => println!("Out of range"),
}
// 참조 패턴
let reference = &4;
match reference {
&val => println!("Got a value via destructuring: {}", val),
}
// 또는 ref 키워드로
let value = 5;
match value {
ref r => println!("Got a reference to {}", r),
}
}
@ 바인딩 활용
fn categorize_block_height(height: u64) -> String {
match height {
// 값을 n에 바인딩하면서 범위 검사
n @ 0 => format!("Genesis block"),
n @ 1..=99 => format!("Early block #{}", n),
n @ 100..=999 => format!("Block #{} (hundreds)", n),
n => format!("Block #{} (large)", n),
}
}
match 가드 (Match Guards)
패턴에 추가 조건을 붙일 수 있습니다:
fn classify_transaction(amount: u64, is_confirmed: bool) -> &'static str {
match (amount, is_confirmed) {
(0, _) => "zero-value transaction",
(_, false) => "unconfirmed",
(amt, true) if amt > 1_000_000 => "large confirmed",
(amt, true) if amt > 10_000 => "medium confirmed",
(_, true) => "small confirmed",
}
}
fn main() {
println!("{}", classify_transaction(0, true)); // zero-value
println!("{}", classify_transaction(5_000, false)); // unconfirmed
println!("{}", classify_transaction(2_000_000, true)); // large confirmed
println!("{}", classify_transaction(50_000, true)); // medium confirmed
println!("{}", classify_transaction(100, true)); // small confirmed
}
if let: 단일 패턴 매칭
match가 한 패턴만 처리할 때, if let이 더 간결합니다:
fn main() {
let some_value: Option<u32> = Some(42);
// match로 쓰면
match some_value {
Some(v) => println!("Got: {}", v),
None => {} // 아무것도 안 함
}
// if let으로 더 간결하게
if let Some(v) = some_value {
println!("Got: {}", v);
}
// else 추가 가능
if let Some(v) = some_value {
println!("Got: {}", v);
} else {
println!("Nothing");
}
// 열거형과 함께
let event = WalletEvent::Deposit { amount: 1000, from: String::from("Alice") };
if let WalletEvent::Deposit { amount, from } = event {
println!("Deposit {} from {}", amount, from);
}
}
블록체인 코드에서 if let 활용
fn get_block_data(blockchain: &Blockchain, index: u64) -> Option<String> {
blockchain.blocks.get(index as usize).map(|b| b.data.clone())
}
fn main() {
let blockchain = Blockchain::new();
// if let으로 깔끔하게 처리
if let Some(data) = get_block_data(&blockchain, 0) {
println!("Genesis data: {}", data);
} else {
println!("Block not found");
}
// 체이닝
if let Some(block) = blockchain.blocks.first() {
if let Some(hash) = block.hash.get(..6) {
println!("Short hash: {}...", hash);
}
}
}
while let: 조건부 반복
fn main() {
let mut stack = vec![1, 2, 3, 4, 5];
// stack.pop()이 Some을 반환하는 동안 반복
while let Some(top) = stack.pop() {
println!("Popped: {}", top);
}
// 5, 4, 3, 2, 1 순서로 출력
// 채널에서 메시지 받기 (tokio/std 채널 패턴)
// while let Ok(msg) = receiver.recv() {
// handle_message(msg);
// }
}
let else (Rust 1.65+)
패턴이 매칭되지 않으면 early return하는 패턴:
fn process_transaction(tx_data: &str) -> Result<(), String> {
// tx_data를 파싱
let parts: Vec<&str> = tx_data.split(':').collect();
// 패턴 매칭 실패시 else 블록 실행 (return/break/continue/panic 필요)
let [from, to, amount_str] = parts.as_slice() else {
return Err(String::from("Invalid transaction format"));
};
let Ok(amount) = amount_str.parse::<u64>() else {
return Err(format!("Invalid amount: {}", amount_str));
};
println!("Transfer {} from {} to {}", amount, from, to);
Ok(())
}
fn main() {
match process_transaction("Alice:Bob:1000") {
Ok(()) => println!("Success"),
Err(e) => println!("Error: {}", e),
}
match process_transaction("invalid") {
Ok(()) => println!("Success"),
Err(e) => println!("Error: {}", e),
}
}
matches! 매크로
bool을 반환하는 패턴 매칭 단축형:
#[derive(PartialEq)]
enum Status { Active, Inactive, Suspended }
fn main() {
let status = Status::Active;
// match로
let is_active = match status {
Status::Active => true,
_ => false,
};
// matches! 매크로로 (더 간결)
let is_active2 = matches!(status, Status::Active);
// 여러 패턴
let is_problematic = matches!(status, Status::Inactive | Status::Suspended);
// 조건 포함
let num = 42i32;
let in_range = matches!(num, 1..=100);
println!("{} {} {} {}", is_active, is_active2, is_problematic, in_range);
}
전체 패턴 종류 요약
fn all_patterns(x: i32) {
match x {
// 1. 리터럴
0 => println!("zero"),
// 2. 변수 (모든 값을 n에 바인딩)
n => println!("n = {}", n),
}
let pair = (1, -1);
match pair {
// 3. 튜플 패턴
(0, y) => println!("First is zero, y={}", y),
(x, 0) => println!("x={}, Second is zero", x),
(x, y) => println!("({}, {})", x, y),
}
// 4. 열거형 패턴 (앞서 설명)
// 5. 구조체 패턴 (앞서 설명)
// 6. 범위 패턴 (앞서 설명)
// 7. @ 바인딩 (앞서 설명)
// 8. 와일드카드 _ (앞서 설명)
// 9. OR 패턴 |
// 10. 가드 if
// 11. ref/ref mut (참조 바인딩)
}
요약
match: 강력한 패턴 매칭 — exhaustive (모든 케이스 강제 처리)if let: 단일 패턴에 간결하게 사용while let: 패턴이 매칭되는 동안 반복let else: 매칭 실패 시 early returnmatches!: bool 반환하는 패턴 매칭 단축형- 구조 분해: 튜플, 구조체, 열거형을 분해해서 내부 값 꺼내기
@바인딩: 패턴 매칭하면서 값을 변수에 바인딩- match 가드:
if조건으로 패턴에 추가 조건 부여
다음으로는 합의 알고리즘을 배운 뒤, 1주차 미니프로젝트로 블록체인을 직접 구현합니다. 에러 처리는 2주차에서 본격적으로 다룹니다.