프로그래밍의 대표적인 컨트롤 플로우라 하면

조건문과 반복문일 것이고

구문으로 말하면 if문과 for문일 것이다


러스트에서도 if와 for문이 존재한다


for n in 1..=10 { // 1부터 10까지 도는 for문 if n % 2 == 1 { println!("{} is odd", n); } else { println!("{} is even", n); } }


위는 if문과 for문을 한번에 보여주는 예시다


특이한 부분이라고 하면 1..=10 이 부분일 것이다


이는 러스트에서 range라고 하는 개념이다


0..10: 0부터 9까지


마지막 숫자는 빠진다

for문에서 i = 0에 i < 10을 쓰는 것과 비슷한 느낌


1..=10: 1부터 10까지


마지막 숫자를 포함하려면 이렇게 써야 한다


다른 언어에서 또 다른 조건문과 반복문이라고 하면

switch와 while이 있을 것이다


러스트에도 while은 있다

그리고 switch는 없지만 match라는 것이 존재한다


그리고 조건식 없는 반복문도 있는데, 바로 loop라고 한다


let mut n = 0; loop { n += 1; // 러스트에서는 ++n, n++이 없다. match n { 10.. => break, // 10 이상이면 탈출 _ => continue, } }


보다시피 아예 무한반복을 가정해서 조건식이 없는 모습이다

탈출하려면 break로 탈출해야 한다


match는 기본적으로 switch와 비슷한 역할을 한다

다른 언어에서 default인 부분은 언더바로 표시한다


c나 c++에서 중첩 반복문을 loop로 탈출하는 경우가 있다

러스트에서는 반복문에 이름을 지정하고

그 반복문을 탈출하는 것이 가능하다


'outer: loop { 'inner: loop { break 'outer; } }


원래 break만 치면 가장 안쪽에 있는 반복문을 탈출하지만

이렇게 레이블링을 하면 더 바깥쪽의 반복문을 탈출하는 것도 가능하다


match에는 또 한 가지 특이한 사용법이 있는데,

그건 바로 배열이나 투플 같은 요소를 부분적으로 사용하는 것이다.


let triple = (0, -2, 3); // 투플 출력은 {:?}를 써야 한다 println!("Tell me about {:?}", triple); match triple { // 첫 요소가 0일 때 // 나머지 두 요소에는 y, z라는 변수명을 사용 (0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z), // 첫 요소가 1일 때 (1, ..) => println!("First is `1` and the rest doesn't matter"), // 마지막 요소가 2일 때 (.., 2) => println!("last is `2` and the rest doesn't matter"), // 첫 요소가 3이고, 마지막 요소가 4일 때 (3, .., 4) => println!("First is `3`, last is `4`, and the rest doesn't matter") // 나머지 경우 _ => println!("It doesn't matter what they are"), }


러스트는 이런 것들에 대한 검사가 잘돼 있어서,

만약 조건이 겹치는 게 있으면 에러까진 아니지만 경고가 뜬다

경고 내용은 첫 번째 경우 이외엔 나머지 것들에 도달하지 않는다는 것이다


마지막으로 if let, while let에 대해 알아보도록 하겠다


러스트에는 null 대신에 std::Option이라는 enum을 사용한다

Option은 Some<T>거나, None이다


그런데 이때 match를 쓰면 언더바 부분이 낭비된다


// Make `optional` of type `Option<i32>` let optional = Some(7); match optional { Some(i) => { println!("This is a really long string and `{:?}`", i); // ^ Needed 2 indentations just so we could destructure // `i` from the option. }, _ => {}, // ^ Required because `match` is exhaustive. Doesn't it seem // like wasted space? };


이때, if let을 사용하면 코드를 좀 줄일 수 있다


fn main() { // number에는 Some(7)이 들어가 있고, letter는 None 상태다 let number = Some(7); let letter: Option<i32> = None; // 이 경우엔 number에 값이 있어서 7이 i가 된다 if let Some(i) = number { println!("Matched {:?}!", i); } // letter는 None 상태라 else에 들어가게 된다 if let Some(i) = letter { println!("Matched {:?}!", i); } else { // Destructure failed. Change to the failure case. println!("Didn't match a number. Let's go with a letter!"); } }


while let은 다음과 같이 활용할 수 있다


fn main() { // optional을 Some(0) 상태로 초기화 let mut optional = Some(0); // optional이 Some 상태인 경우에만 루프 while let Some(i) = optional { if i > 9 { // 9보다 커지면 None을 대입하고 탈출 println!("Greater than 9, quit!"); optional = None; } else { // Some 내부의 크기를 1씩 증가시킨다 println!("`i` is `{:?}`. Try again.", i); optional = Some(i + 1); } }


나도 러스트엔 익숙치가 않아서

이러한 것들의 존재가 다 귀찮게만 느껴질 수 있을 거라는 점 백번 이해한다


하지만 익숙해진다고 가정하고 보면

이러한 것들의 존재는 흔하게 치던 코드들을

훨씬 더 짧게 줄여줘 생산성을 높일 수 있다고 생각한다


게다가 러스트는 무비용, 저비용 추상화를 지향하기 때문에

이런 기능을 자주 쓴다고 해서 특별히 성능이 떨어지지도 않는다

이런 것도 현대 프로그래밍 언어의 장점이라고 할 수 있을 것이다


이제 마무리를 하겠다


1편에서는 데이터 타입, 2편에서는 컨트롤 플로우에 대해 알아보았다

둘은 프로그래밍에서 가장 핵심적인 개념이라고도 할 수 있는 것들이기 때문에,

사실 이것만 어느 정도 익혀도 입문할 준비 자체는 됐다고도 볼 수 있다


다만 이것으로 프로그램을 만들려면 몇 가지 더 필요한 게 있는데,

바로 콘솔에서의 입출력이다


3편에서는 어렴풋이 넘겼던 println!의 좀 더 구체적인 사용법,

그리고 사용자의 입력을 받는 방법에 대해서 알아도보록 하겠다.