고급 제어 흐름

01에서 loop/while/for 기본을 다뤘고, 02에서 이터레이터를 다뤘다. 이번 글에서는 while let, 루프 라벨, 그리고 러스트만의 독특한 제어 흐름 패턴들을 정리한다.

1. while let - 패턴이 매칭되는 동안 반복

if let의 반복문 버전이다. 패턴이 매칭되는 동안 계속 루프를 돈다.

let mut stack = vec![1, 2, 3, 4, 5];
 
// pop()이 Some을 반환하는 동안 반복
while let Some(top) = stack.pop() {
    println!("{}", top);
}
// 출력: 5, 4, 3, 2, 1

while let vs while + match

// while let 없이
loop {
    match stack.pop() {
        Some(top) => println!("{}", top),
        None => break,
    }
}
 
// while let으로 깔끔하게
while let Some(top) = stack.pop() {
    println!("{}", top);
}

채널에서 메시지 받기 (실전)

use std::sync::mpsc;
use std::thread;
 
let (tx, rx) = mpsc::channel();
 
thread::spawn(move || {
    for msg in ["hello", "world", "done"] {
        tx.send(msg).unwrap();
    }
});
 
// 채널이 열려 있는 동안 메시지를 계속 받는다
while let Ok(msg) = rx.recv() {
    println!("수신: {}", msg);
}

이터레이터의 .next()와 함께

let mut iter = [10, 20, 30].iter();
 
while let Some(val) = iter.next() {
    println!("{}", val);
}

사실 이건 for val in iter와 같다. 하지만 while let은 이터레이터 외의 패턴에도 쓸 수 있어서 더 범용적이다.

2. 루프 라벨 (Loop Label)

중첩된 루프에서 바깥 루프를 제어할 때 쓴다.

'outer: for i in 0..5 {
    for j in 0..5 {
        if i + j == 6 {
            println!("탈출: i={}, j={}", i, j);
            break 'outer;  // 바깥 루프를 탈출
        }
    }
}

breakcontinue는 기본적으로 가장 안쪽 루프에만 적용된다. 라벨을 붙이면 어떤 루프를 대상으로 할지 명시할 수 있다.

continue에도 라벨

'outer: for i in 0..3 {
    for j in 0..3 {
        if j == 1 {
            continue 'outer;  // 안쪽 루프가 아닌 바깥 루프의 다음 반복으로
        }
        println!("i={}, j={}", i, j);
    }
}
// 출력:
// i=0, j=0
// i=1, j=0
// i=2, j=0

라벨 + loop에서 값 반환

let result = 'search: loop {
    for row in 0..10 {
        for col in 0..10 {
            if row * col == 42 {
                break 'search (row, col);  // 라벨 루프에서 값 반환
            }
        }
    }
    break 'search (0, 0);  // 찾지 못한 경우
};
 
println!("찾은 위치: {:?}", result);

3. loop + match 패턴 (상태 머신)

loop 안에서 match를 쓰면 상태 머신을 깔끔하게 표현할 수 있다.

enum State {
    Init,
    Running(u32),
    Paused,
    Done,
}
 
let mut state = State::Init;
 
loop {
    state = match state {
        State::Init => {
            println!("초기화...");
            State::Running(0)
        }
        State::Running(n) if n >= 5 => {
            println!("완료 조건 도달");
            State::Done
        }
        State::Running(n) => {
            println!("실행 중: {}", n);
            if n == 3 {
                State::Paused
            } else {
                State::Running(n + 1)
            }
        }
        State::Paused => {
            println!("일시정지 → 재개");
            State::Running(4)
        }
        State::Done => {
            println!("종료");
            break;
        }
    };
}

4. 범위(Range) 패턴 총정리

for 루프에서 자주 쓰는 범위 문법을 정리한다.

// 반개방 범위 (끝 미포함)
for i in 0..5 {
    // 0, 1, 2, 3, 4
}
 
// 닫힌 범위 (끝 포함)
for i in 0..=5 {
    // 0, 1, 2, 3, 4, 5
}
 
// 역순
for i in (0..5).rev() {
    // 4, 3, 2, 1, 0
}
 
// step_by — n칸씩 건너뛰기
for i in (0..20).step_by(3) {
    // 0, 3, 6, 9, 12, 15, 18
}
 
// 문자 범위
for c in 'a'..='f' {
    // 'a', 'b', 'c', 'd', 'e', 'f'
}

5. for와 인덱스

러스트에서는 인덱스 기반 루프보다 이터레이터를 권장하지만, 인덱스가 필요할 때 enumerate를 쓴다.

let items = vec!["apple", "banana", "cherry"];
 
// 나쁜 예: 인덱스로 접근
for i in 0..items.len() {
    println!("{}: {}", i, items[i]);  // 범위 오류 가능성
}
 
// 좋은 예: enumerate
for (i, item) in items.iter().enumerate() {
    println!("{}: {}", i, item);  // 안전
}

두 컬렉션을 동시에 순회할 때:

let keys = vec!["name", "age", "city"];
let values = vec!["ferris", "10", "ocean"];
 
// zip으로 병렬 순회
for (key, value) in keys.iter().zip(values.iter()) {
    println!("{} = {}", key, value);
}

6. loop를 활용한 재시도 패턴

fn unreliable_operation() -> Result<String, String> {
    // 가끔 실패하는 작업
    Err("네트워크 에러".into())
}
 
let result = {
    let mut attempts = 0;
    loop {
        attempts += 1;
        match unreliable_operation() {
            Ok(data) => break data,
            Err(e) if attempts < 3 => {
                println!("시도 {} 실패: {}. 재시도...", attempts, e);
                continue;
            }
            Err(e) => {
                panic!("{}회 시도 후 최종 실패: {}", attempts, e);
            }
        }
    }
};

7. matches! 매크로

matchif let으로 쓰기엔 과한, 단순한 패턴 확인에 쓴다.

let value = Some(42);
 
// if let
if let Some(_) = value {
    println!("값이 있다");
}
 
// matches! — 더 간결
if matches!(value, Some(_)) {
    println!("값이 있다");
}
 
// 복잡한 패턴도 가능
let status = 404;
let is_client_error = matches!(status, 400..=499);
 
let ch = 'A';
let is_alphanumeric = matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9');

matches!bool을 반환해서 .filter()나 조건문에서 바로 쓸 수 있다.

let items = vec![Some(1), None, Some(3), None, Some(5)];
 
let count = items.iter().filter(|x| matches!(x, Some(_))).count();
println!("Some 개수: {}", count);  // 3

정리

문법용도
while let패턴 매칭이 성공하는 동안 반복
'label: loop중첩 루프에서 바깥 루프 제어
loop + match상태 머신 구현
(0..n).step_by(k)k칸씩 건너뛰며 순회
enumerate인덱스 + 값 동시 순회
zip두 컬렉션 병렬 순회
matches!간단한 패턴 확인 (bool 반환)

러스트의 제어 흐름은 패턴 매칭과 깊이 연결되어 있다. if let, while let, match, let-else, matches! 모두 같은 패턴 시스템 위에서 동작한다. 이 점을 이해하면 어떤 상황에서 어떤 구문을 쓸지 자연스럽게 결정할 수 있다.