#programminglanguage #rust > [!introduction] > 러스트에는 조건에 따라 다른 코드를 실행하거나 특정 구간을 반복하는 등 프로그램의 실행을 통제하는 제어 흐름문이 있습니다. 이번 글에서는 러스트의 제어 흐름문 및 프로그램의 실행을 제어하기 위한 대표적인 문법과 자료형을 살펴봅니다. ## `if` 조건에 따라 다른 코드를 실행해야 할 때 `if`문을 통해 조건에 따른 코드의 분기*branch*를 간단하게 구현할 수 있다. 모든 `if` 표현식은 `if`라는 키워드에서 시작하고 그 뒤에 `bool` 타입을 반환하는 조건식이 온다. ```rust fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } } ``` `number < 5`처럼 `if` 뒤에 따라오는 조건식은 반드시 `bool` 타입을 반환해야 하는데, 러스트는 C처럼 다른 타입에서 자동으로 참/거짓을 판단하지 않기 때문에 조건식이 반환하는 값에 유의해야 한다. 만약 조건식이 `bool` 타입을 반환하지 않는 경우 에러가 발생한다. ```rust fn main() { let number = 3; if number { println!("number was three"); } } ``` ```bash $ cargo run Compiling branches v0.1.0 (file:///projects/branches) error[E0308]: mismatched types --> src/main.rs:4:8 | 4 | if number { | ^^^^^^ expected `bool`, found integer For more information about this error, try `rustc --explain E0308`. error: could not compile `branches` due to previous error ``` ### `else if` - `else` 추가로 `else if`를 통해 또 다른 분기를 추가할 수 있고, `else`를 통해 조건에 맞지 않는 나머지 케이스를 처리할 수 있다. ```rust fn main() { let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3, or 2"); } } ``` ### `if`는 표현식이다 한편 `if`는 [[프로그래밍 언어/Rust/함수#^a14250\|표현식]]에 해당하기 때문에 값을 반환한다. 이를 활용해 변수를 생성하는 `let` 구문의 우변에 사용할 수 있다. ```rust fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {number}"); } ``` 이렇게 조건에 따라 다른 값을 넣을 수도 있다. 다만 조건에 따라 타입이 달라지면 변수의 타입이 실행 이후, 즉 런타임*runtime*에 정의되어야 하기 때문에 실행 이전에 모든 검증이 끝나는 것을 지향하는 러스트에서는 이런 형태의 코드를 허용하지 않는다. ## `loop` 러스트에서 가장 단순한 반복문은 `loop`다. 이 키워드를 사용하면 따로 멈추라는 말이 없으면 여기에 연결된 코드 블록을 계속 반복해서 실행한다. ```rust fn main() { loop { println!("again!"); } } ``` 위와 같은 코드를 실행하면 직접 반복을 중지시키기 전까지 프로그램은 계속 터미널에 `again!`을 출력한다. 프로그램을 멈추게 하려면 조건이 포함된 반복문으로 고치거나 터미널에서 강제로 중지해야 한다. ```rust $ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev [unoptimized + debuginfo] target(s) in 0.29s Running `target/debug/loops` again! again! again! again! ^Cagain! ``` 터미널에서 실행 중인 프로그램을 강제로 중지시키는 방법은 보통 `Ctrl`키와 `C`키를 동시에 누르는 것이다. ### `break` 또 다른 방법은 조건과 함께 `break`라는 키워드를 추가하는 것이다. 반복문을 탈출하기 위한 조건을 `if`로 작성하고 `break`를 그 안에 넣으면 조건이 맞는 상황에서 반복문을 탈출할 수 있다. ```rust fn main() { let mut counter = 0; loop { counter += 1; if counter == 10 { break; } println!("The result is {counter}"); }; } ``` 위 코드를 실행하면 아래와 같이 `counter`가 10이 되었을 때 `loop` 구문을 탈출한다. ```bash revenantonthemission@MacBook-Pro-3 loops % cargo run Compiling loops v0.1.0 (/Users/revenantonthemission/rustprojects/loops) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.46s Running `target/debug/loops` The result is 1 The result is 2 The result is 3 The result is 4 The result is 5 The result is 6 The result is 7 The result is 8 The result is 9 ``` 그런데 러스트에서는 `break`를 통해 반복문을 탈출하는 동시에 값을 반환할 수 있다. 그리고 이 값을 변수에 할당할 수 있다. ```rust fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("The result is {result}"); } ``` 또한 반복문에 라벨*loop label*을 추가해 어떤 반복문을 탈출할지 명시할 수도 있다. `break` 뒤에 라벨을 붙이면 특정 반복문을 정하여 해당 반복문을 탈출할 수 있다. ```rust fn main() { let mut count = 0; 'counting_up: loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {count}"); } ``` ## `while` 특정 조건을 만나고 나서 반복을 중지하는 이 패턴을 줄일 수 있는데, 러스트에서는 `while`을 통해 그 방법을 제공한다. ```rust fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } println!("LIFTOFF!!!"); } ``` `while`문에서는 반복할 때마다 자동으로 뒤에 붙는 조건식을 확인하는데, 그 값이 `true`일 경우 코드 블록을 실행하고 다시 조건식으로 돌아오며 `false`인 경우 종료한다. ## `for` `for`는 다른 반복문인 `loop`이나 `while`에 비교했을 때 반복할 구간이 정해져 있을 때 사용하기 편하다. 러스트의 `for`구문은 반복할 어떤 구간과 함께 사용해야 하며, 배열이나 튜플 같은 다양한 타입이 이러한 구간을 제공한다. ```rust fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } } ``` 실제로 사용할 때는 위와 같이 반복할 구간 안*in*에 대하여*for* 코드를 수행한다는 의미로 `for`와 함께 `in`이라는 키워드를 사용한다. ## `match` ^7756f5 러스트에서 다양한 케이스를 제어하는 방법에는 `if`, `else if`, `else`도 있지만, `match`를 사용하면 보다 더 많은 경우의 수를 포괄할 수 있다. `if`와 달리 `b ool` 이외의 타입도 조건으로 사용할 수 있고, 코드 자체도 더욱 잘 보인다. 이런 특성을 가지기 때문에 `match` 구문은 [[열거형\|열거형]]*enumeration*과 함께 사용하는 것이 일반적이다. ### `match`와 열거형 열거형과 `match` 표현식을 같이 사용하는 대표적인 케이스는 아래와 같다. ```rust enum UsState { Alabama, Alaska, // --생략-- } enum Coin { Penny, Nickel, Dime, Quarter(UsState), } ``` `match`라는 키워드 뒤에는 평가의 대상이 되는 변수가 따라온다. 이 변수가 가지는 경우의 수에 따라 다른 작업을 하게 되는데, 중괄호`{}` 안에 변수가 가질 수 있는 커이스를 작성한 다음 화살표`=>` 뒤에 해당 케이스에서 작업할 내용을 적으면 된다. 위 코드의 경우 함수 내부에서 경우에 따라 다른 값을 반환하도록 했기 때문에 화살표 뒤에 반환할 값이 들어간 경우다. 만약 열거형의 어떤 배리언트*variant*가 값을 담는 경우, 해당 케이스를 담는 `match`에서는 해당 값을 매개변수로 하는 함수와 같은 코드를 작성할 수 있다. ```rust fn value_in_cents(coin: Coin) -> u8 { match coin { Coin::Penny => 1, Coin::Nickel => 5, Coin::Dime => 10, Coin::Quarter(state) => { println!("State quarter from {:?}!", state); 25 } } } ``` ### 예외 처리 `match` 구문을 작성할 때는 예외 처리에 유의해야 하는데, 모든 케이스를 다루어야 한다. 만약 다루지 않는 케이스가 있다면, 러스트에서는 컴파일 에러가 발생한다. 이를 위해 `else`처럼 특별히 다루지 않은 모든 케이스를 다룰 때 사용하는 것이 바로 `_`라는 키워드다. 어차피 마지막 패턴이 나머지 모든 케이스를 다루긴 하지만, 일반적으로 그 값을 사용할 필요가 없을 때 `_`를 사용한다. ```rust let dice_roll = 9; match dice_roll { 3 => add_fancy_hat(), 7 => remove_fancy_hat(), _ => reroll(), } fn add_fancy_hat() {} fn remove_fancy_hat() {} fn reroll() {} ``` 또한 나머지 케이스를 다룰 때 아무것도 하고 싶지 않다면 소괄호`()`만 사용하면 된다. 이때 그냥 비워버리면 컴파일 에러가 발생하니 작성할 때 주의해야 한다. ```rust let config_max = Some(3u8); match config_max { Some(max) => println!("The maximum is configured to be {}", max), _ => (), } ``` ### `if let` : 더욱 간결하게! ^d9e007 `if let`은 `match`를 사용하는 특정 케이스를 요약해주는 일종의 부가적인 문법*syntax sugar*인데, 위와 같이 하나의 케이스만 특별히 다루고 나머지 케이스를 한꺼번에 다루는 상황에서 코드의 길이를 줄여주는 역할을 한다. ```rust let config_max = Some(3u8); if let Some(max) = config_max { println!("The maximum is configured to be {}", max); } ``` 만약 나머지 케이스에서도 해야 할 일이 있다면 `else`를 같이 사용할 수도 있다. ```rust let mut count = 0; if let Coin::Quarter(state) = coin { println!("State quarter from {:?}!", state); } else { count += 1; } ``` --- ## 참고 자료 & 더보기 + [The Rust Programming Language(한국어판), 3.5. 제어 흐름문](https://doc.rust-kr.org/ch03-05-control-flow.html) + [The Rust Programming Language(한국어판), 6. 열거형과 패턴 매칭](https://doc.rust-kr.org/ch06-00-enums.html)