루프와 그 밖의 중단 가능한 표현식
루프와 그 밖의 중단 가능한 표현식 (Loops and other breakable expressions)
Rust는 반복을 위한 루프 표현식이 여러 가지예요. 무한 루프인 loop, 조건을 검사하는 while, 이터레이터를 도는 for, 그리고 정확히 한 번 실행되면서 중간에 break로 빠져나갈 수 있는 레이블 블록까지. 여기서는 이 네 가지 루프와 break·continue, 그리고 루프에서 값을 돌려받는 방법을 살펴볼게요.
출처: Rust Reference
본문
구문 (Syntax)
LoopExpression →
LoopLabel? (
InfiniteLoopExpression
| PredicateLoopExpression
| IteratorLoopExpression
| LabelBlockExpression
)
Rust는 네 가지 루프 표현식을 지원해요.
loop표현식은 무한 루프를 나타내요.while표현식은 조건(predicate)이 거짓이 될 때까지 반복해요.for표현식은 이터레이터에서 값을 꺼내서, 이터레이터가 비워질 때까지 반복해요.- 레이블 블록 표현식(labeled block expression) 은 루프를 정확히 한 번 실행하되,
break로 루프를 일찍 빠져나갈 수 있어요.
네 가지 루프 모두 break 표현식과 레이블을 지원해요. 레이블 블록 표현식을 제외한 나머지는 모두 continue 표현식을 지원하고요. 비자명한 값(non-trivial values)으로 평가될 수 있는 것은 loop와 레이블 블록 표현식뿐이에요.
무한 루프 (Infinite loops)
InfiniteLoopExpression → loop BlockExpression
loop 표현식은 몸체의 실행을 계속해서 반복해요: loop { println!("I live."); }.
연관된 break 표현식이 없는 loop 표현식은 발산(diverging) 하고 타입이 !이에요. 반대로 연관된 break 표현식을 가진 loop 표현식은 종료될 수 있고, 그 타입은 break 표현식(들)의 값과 호환되어야 해요.
조건 루프 (Predicate loops)
PredicateLoopExpression → while Conditions BlockExpression
while 루프 표현식은 조건 집합이 참인 동안 블록의 평가를 반복하게 해 줘요.
조건 피연산자는 boolean 타입의 Expression이거나 조건부 let 일치(conditional let match)여야 해요. 모든 조건 피연산자가 참으로 평가되고 모든 let 패턴이 스크루티니(scrutinee)와 성공적으로 일치하면, 루프 몸체 블록이 실행돼요. 몸체가 성공적으로 실행된 후에는 조건 피연산자를 다시 평가해서 몸체를 다시 실행할지 결정해요. 어떤 조건 피연산자가 거짓으로 평가되거나 어떤 let 패턴이 스크루티니와 일치하지 않으면, 몸체는 실행되지 않고 실행은 while 표현식 뒤에서 계속돼요.
while 표현식은 ()로 평가돼요.
#![allow(unused)]
fn main() {
let mut i = 0;
while i < 10 {
println!("hello");
i = i + 1;
}
}
while let 패턴 (while let patterns)
while 조건의 let 패턴은 패턴이 성공적으로 일치할 때 새 변수를 스코프에 바인딩할 수 있어요.
#![allow(unused)]
fn main() {
let mut x = vec![1, 2, 3];
while let Some(y) = x.pop() {
println!("y = {}", y);
}
while let _ = 5 {
println!("Irrefutable patterns are always true");
break;
}
}
while let 루프는 다음처럼 match 표현식을 담은 loop 표현식과 동등해요.
'label: while let PATS = EXPR {
/* loop body */
}
는 다음과 동등해요.
'label: loop {
match EXPR {
PATS => { /* loop body */ },
_ => break,
}
}
여러 패턴은 | 연산자로 지정할 수 있어요. 이는 match 표현식에서 |를 쓰는 것과 같은 의미를 가져요.
#![allow(unused)]
fn main() {
let mut vals = vec![2, 3, 1, 2, 2];
while let Some(v @ 1) | Some(v @ 2) = vals.pop() {
// Prints 2, 2, then 1
println!("{}", v);
}
}
while 조건 체인 (while condition chains)
여러 조건 피연산자는 &&로 구분할 수 있어요. 이는 if 조건 체인과 같은 의미와 제약을 가져요. 다음 예시는 여러 표현식을 연결하면서 let 바인딩과 boolean 표현식을 섞고, 뒤의 표현식이 앞 표현식의 패턴 바인딩을 참조할 수 있는 경우예요.
fn main() {
let outer_opt = Some(Some(1i32));
while let Some(inner_opt) = outer_opt
&& let Some(number) = inner_opt
&& number == 1
{
println!("Peek a boo");
break;
}
}
이터레이터 루프 (Iterator loops)
IteratorLoopExpression →
for Pattern in Expressionexcept StructExpression BlockExpression
for 표현식은 std::iter::IntoIterator 구현이 제공하는 요소들을 반복하는 구문적 구조예요.
이터레이터가 값을 생산하면 그 값은 비반박 패턴(irrefutable pattern) 과 일치시키고, 루프 몸체를 실행한 뒤 for 루프의 머리로 제어가 돌아가요. 이터레이터가 비어 있으면 for 표현식이 완료돼요.
배열의 내용을 도는 for 루프 예시예요.
#![allow(unused)]
fn main() {
let v = &["apples", "cake", "coffee"];
for text in v {
println!("I like {}.", text);
}
}
정수 범위를 도는 예시예요.
#![allow(unused)]
fn main() {
let mut sum = 0;
for n in 1..11 {
sum += n;
}
assert_eq!(sum, 55);
}
for 루프는 다음과 같이 match 표현식을 담은 loop 표현식과 동등해요.
'label: for PATTERN in iter_expr {
/* loop body */
}
는 다음과 동등해요.
{
let result = match IntoIterator::into_iter(iter_expr) {
mut iter => 'label: loop {
let mut next;
match Iterator::next(&mut iter) {
Option::Some(val) => next = val,
Option::None => break,
};
let PATTERN = next;
let () = { /* loop body */ };
},
};
result
}
여기서 IntoIterator, Iterator, Option은 항상 표준 라이브러리 아이템이지, 현재 스코프에서 그 이름들이 해석되는 대상은 아니에요. 변수 이름 next, iter, val은 설명을 위한 것일 뿐, 사용자가 실제로 타이핑할 수 있는 이름이 아니에요.
참고: 바깥쪽
match는iter_expr의 임시 값들이 루프가 끝나기 전에 drop되지 않도록 보장하기 위한 것이에요.next는 할당되기 전에 선언되는데, 그렇게 해야 타입이 더 자주 올바르게 추론되기 때문이에요.
루프 레이블 (Loop labels)
LoopLabel → LIFETIME_OR_LABEL :
loop 표현식은 선택적으로 레이블을 가질 수 있어요. 레이블은 루프 표현식 앞에 오는 수명(lifetime)으로 쓰는데, 'foo: loop { break 'foo; }, 'bar: while false {}, 'humbug: for _ in 0..0 {}처럼요.
레이블이 있으면, 이 루프 안에 중첩된 레이블이 붙은 break·continue 표현식이 이 루프를 빠져나가거나 제어를 루프 머리로 돌려보낼 수 있어요.
레이블은 지역 변수의 위생(hygiene)과 가림(shadowing) 규칙을 따라요. 예를 들어 이 코드는 "outer loop"를 출력해요.
#![allow(unused)]
fn main() {
'a: loop {
'a: loop {
break 'a;
}
print!("outer loop");
break 'a;
}
}
'_는 유효한 루프 레이블이 아니에요.
break 표현식 (break expressions)
BreakExpression → break LIFETIME_OR_LABEL? Expression?
break를 만나면 연관된 루프 몸체의 실행이 즉시 종료돼요.
#![allow(unused)]
fn main() {
let mut last = 0;
for x in 1..100 {
if x > 12 {
break;
}
last = x;
}
assert_eq!(last, 12);
}
break 표현식은 발산하고 타입이 !이에요.
break 표현식은 보통 그것을 감싸는 가장 안쪽의 loop·for·while 루프와 연관되지만, 레이블로 어느 바깥 루프에 영향을 줄지 지정할 수 있어요.
#![allow(unused)]
fn main() {
'outer: loop {
while true {
break 'outer;
}
}
}
break 표현식은 루프 몸체에서만 허용되며, break, break 'label, break EXPR 또는 break 'label EXPR 형태를 가져요.
break 표현식을 가진 loop나 레이블 블록 표현식에서, 표현식 없는 break는 break ()와 동등해요.
레이블 블록 표현식 (Labeled block expressions)
LabelBlockExpression → BlockExpression
레이블 블록 표현식은 일반 블록 표현식과 정확히 같지만, 블록 안에서 break 표현식을 사용하는 것을 허용해요.
루프와 달리, 레이블 블록 표현식 안의 break 표현식은 반드시 레이블을 가져야 해요(즉 레이블이 선택 사항이 아니에요). 마찬가지로, 레이블 블록 표현식은 반드시 레이블로 시작해야 해요.
#![allow(unused)]
fn main() {
fn do_thing() {}
fn condition_not_met() -> bool { true }
fn do_next_thing() {}
fn do_last_thing() {}
let result = 'block: {
do_thing();
if condition_not_met() {
break 'block 1;
}
do_next_thing();
if condition_not_met() {
break 'block 2;
}
do_last_thing();
3
};
}
레이블 블록 표현식의 타입은 모든 break 피연산자와 최종 피연산자의 최소 상계(least upper bound) 이야. 최종 피연산자를 생략하면 그 타입은 유닛 타입으로 기본 설정되는데, 블록이 발산하면 그 경우에는 never 타입이 돼요.
#![allow(unused)]
fn main() {
fn example(condition: bool) {
let s = String::from("owned");
let _: &str = 'block: {
if condition {
break 'block &s; // &String coerced to &str via Deref
}
break 'block "literal"; // &'static str coerced to &str
};
}
}
continue 표현식 (continue expressions)
ContinueExpression → continue LIFETIME_OR_LABEL?
continue를 만나면 연관된 루프 몸체의 현재 반복이 즉시 종료되고, 제어가 루프 머리(loop head)로 돌아가요. continue 표현식은 발산하고 타입이 !이에요.
while 루프의 경우 머리는 루프를 제어하는 조건 피연산자들이고, for 루프의 경우 머리는 루프를 제어하는 호출 표현식(call-expression)이에요.
break처럼 continue도 보통 그것을 감싸는 가장 안쪽 루프와 연관되지만, continue 'label로 영향을 줄 루프를 지정할 수 있어요. continue 표현식은 루프 몸체에서만 허용돼요.
break와 loop 값 (break and loop values)
loop와 연관될 때, break 표현식은 break EXPR 또는 break 'label EXPR 형태로 루프에서 값을 돌려줄 수 있어요. 여기서 EXPR의 결과가 루프에서 반환돼요. 예를 들어 피보나치 수열에서 10을 넘는 첫 번째 수를 찾아요.
#![allow(unused)]
fn main() {
let (mut a, mut b) = (1, 1);
let result = loop {
if b > 10 {
break b;
}
let c = a + b;
a = b;
b = c;
};
// first number in Fibonacci sequence over 10:
assert_eq!(result, 13);
}
연관된 break 표현식을 가진 loop의 타입은 모든 break 피연산자의 최소 상계이에요.
#![allow(unused)]
fn main() {
fn example(condition: bool) {
let s = String::from("owned");
let _: &str = loop {
if condition {
break &s; // &String coerced to &str via Deref
}
break "literal"; // &'static str coerced to &str
};
}
}
연관된 break 표현식을 가진 loop는 break 피연산자 중 하나라도 발산하지 않으면 발산하지 않아요. 모든 break 피연산자가 발산하면 루프 표현식도 발산해요.
#![allow(unused)]
fn main() {
fn diverging_loop_with_break(condition: bool) -> ! {
// This loop is diverging because all `break` operands are diverging.
loop {
if condition {
break loop {};
} else {
break panic!();
}
}
}
}
#![allow(unused)]
fn main() {
fn loop_with_non_diverging_break(condition: bool) -> ! {
// The type of this loop is i32 even though one of the breaks is
// diverging.
loop {
if condition {
break loop {};
} else {
break 123i32;
}
} // ERROR: expected `!`, found `i32`
}
}
두 번째 예시에서처럼, break 피연산자 중 하나(123i32)가 발산하지 않으면 루프 전체 타입이 i32가 되어 !를 기대하는 함수 반환 타입과 어긋나서 에러가 나요.
더 알아보기 (Learn more)
- 표현식 (Expressions) — 루프가 속한 표현식 전체
- 블록과 제어 흐름 표현식 —
if/else,match등과의 관계 - 이터레이터 (Iterators) —
for루프가 의존하는IntoIterator