소개
Control Flow에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 Rust 의 제어 흐름에 중점을 둘 것입니다. 여기에는 조건에 따라 코드를 실행하고 조건이 참인 동안 코드를 반복하기 위해 if 표현식과 루프를 사용하는 것이 포함됩니다.
Control Flow에 오신 것을 환영합니다. 이 랩은 Rust Book의 일부입니다. LabEx 에서 Rust 기술을 연습할 수 있습니다.
이 랩에서는 Rust 의 제어 흐름에 중점을 둘 것입니다. 여기에는 조건에 따라 코드를 실행하고 조건이 참인 동안 코드를 반복하기 위해 if 표현식과 루프를 사용하는 것이 포함됩니다.
어떤 조건이 true인지에 따라 일부 코드를 실행하고, 조건이 true인 동안 일부 코드를 반복적으로 실행하는 능력은 대부분의 프로그래밍 언어에서 기본적인 구성 요소입니다. Rust 코드의 실행 흐름을 제어할 수 있게 해주는 가장 일반적인 구조는 if 표현식과 루프입니다.
if 표현식을 사용하면 조건에 따라 코드를 분기할 수 있습니다. 조건을 제공한 다음 "이 조건이 충족되면 이 코드 블록을 실행합니다. 조건이 충족되지 않으면 이 코드 블록을 실행하지 않습니다."라고 명시합니다.
if 표현식을 탐색하기 위해 project 디렉토리에서 branches라는 새 프로젝트를 만듭니다. src/main.rs 파일에 다음을 입력합니다.
cd ~/project
cargo new branches
파일 이름: src/main.rs
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
모든 if 표현식은 키워드 if로 시작하고, 그 뒤에 조건이 옵니다. 이 경우 조건은 변수 number의 값이 5 미만인지 확인합니다. 조건이 true인 경우 실행할 코드 블록을 중괄호 안에 조건 바로 뒤에 배치합니다. if 표현식의 조건과 관련된 코드 블록은 때때로 "Guess 를 Secret Number 와 비교하기"에서 논의한 match 표현식의 arm 과 마찬가지로 arm이라고도 합니다.
선택적으로, 조건이 false로 평가될 경우 실행할 대체 코드 블록을 프로그램에 제공하기 위해 여기에서 선택한 else 표현식을 포함할 수도 있습니다. else 표현식을 제공하지 않고 조건이 false이면 프로그램은 if 블록을 건너뛰고 다음 코드 비트로 이동합니다.
이 코드를 실행해 보십시오. 다음 출력이 표시됩니다.
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was true
number의 값을 조건이 false가 되도록 변경하여 어떤 일이 발생하는지 살펴보겠습니다.
let number = 7;
프로그램을 다시 실행하고 출력을 확인합니다.
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was false
또한 이 코드의 조건은 반드시 bool이어야 한다는 점에 유의해야 합니다. 조건이 bool이 아니면 오류가 발생합니다. 예를 들어, 다음 코드를 실행해 보십시오.
파일 이름: src/main.rs
fn main() {
let number = 3;
if number {
println!("number was three");
}
}
if 조건은 이번에는 3의 값으로 평가되고 Rust 는 오류를 발생시킵니다.
$ 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
오류는 Rust 가 bool을 예상했지만 정수를 받았음을 나타냅니다. Ruby 및 JavaScript 와 같은 언어와 달리 Rust 는 부울이 아닌 유형을 자동으로 부울로 변환하려고 시도하지 않습니다. 명시적으로 항상 if에 부울을 조건으로 제공해야 합니다. 예를 들어, 숫자가 0과 같지 않을 때만 if 코드 블록이 실행되도록 하려면 if 표현식을 다음과 같이 변경할 수 있습니다.
파일 이름: src/main.rs
fn main() {
let number = 3;
if number != 0 {
println!("number was something other than zero");
}
}
이 코드를 실행하면 number was something other than zero가 출력됩니다.
else if 표현식에서 if와 else를 결합하여 여러 조건을 사용할 수 있습니다. 예를 들어:
파일 이름: src/main.rs
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");
}
}
이 프로그램에는 실행할 수 있는 네 가지 경로가 있습니다. 실행 후 다음 출력을 볼 수 있습니다.
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
number is divisible by 3
이 프로그램이 실행되면 각 if 표현식을 차례로 확인하고 조건이 true로 평가되는 첫 번째 본문을 실행합니다. 6 이 2 로 나누어 떨어지더라도 number is divisible by 2 출력이 표시되지 않으며, else 블록의 number is not divisible by 4, 3, or 2 텍스트도 표시되지 않습니다. 이는 Rust 가 첫 번째 true 조건에 대한 블록만 실행하고, 하나를 찾으면 나머지는 확인하지 않기 때문입니다.
else if 표현식을 너무 많이 사용하면 코드가 복잡해질 수 있으므로, 여러 개가 있는 경우 코드를 리팩터링하는 것이 좋습니다. 6 장에서는 이러한 경우에 사용할 수 있는 강력한 Rust 분기 구조인 match에 대해 설명합니다.
if는 표현식이므로, Listing 3-2 와 같이 결과를 변수에 할당하기 위해 let 문의 오른쪽에 사용할 수 있습니다.
파일 이름: src/main.rs
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
}
Listing 3-2: if 표현식의 결과를 변수에 할당하기
number 변수는 if 표현식의 결과에 따라 값에 바인딩됩니다. 이 코드를 실행하여 어떤 일이 발생하는지 확인하십시오.
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/branches`
The value of number is: 5
코드 블록은 마지막 표현식으로 평가되고, 숫자 자체도 표현식이라는 것을 기억하십시오. 이 경우 전체 if 표현식의 값은 어떤 코드 블록이 실행되는지에 따라 달라집니다. 즉, if의 각 arm 에서 결과가 될 수 있는 값은 동일한 유형이어야 합니다. Listing 3-2 에서 if arm 과 else arm 의 결과는 모두 i32 정수였습니다. 다음 예와 같이 유형이 일치하지 않으면 오류가 발생합니다.
파일 이름: src/main.rs
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {number}");
}
이 코드를 컴파일하려고 하면 오류가 발생합니다. if와 else arm 은 호환되지 않는 값 유형을 가지며, Rust 는 프로그램에서 문제가 있는 정확한 위치를 나타냅니다.
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:4:44
|
4 | let number = if condition { 5 } else { "six" };
| - ^^^^^ expected integer, found
`&str`
| |
| expected because of this
if 블록의 표현식은 정수로 평가되고, else 블록의 표현식은 문자열로 평가됩니다. 변수는 단일 유형을 가져야 하고, Rust 는 컴파일 시간에 number 변수의 유형을 확실하게 알아야 하므로 이 방법은 작동하지 않습니다. number의 유형을 알면 컴파일러가 number를 사용하는 모든 곳에서 유형이 유효한지 확인할 수 있습니다. number의 유형이 런타임에만 결정된다면 Rust 는 그렇게 할 수 없을 것입니다. 컴파일러는 더 복잡해지고, 모든 변수에 대해 여러 가상 유형을 추적해야 하는 경우 코드에 대한 보장을 줄일 것입니다.
코드 블록을 여러 번 실행하는 것이 유용한 경우가 많습니다. 이 작업을 위해 Rust 는 여러 *루프 (loop)*를 제공하며, 루프 본문 내의 코드를 끝까지 실행한 다음 즉시 처음부터 다시 시작합니다. 루프를 실험하기 위해 loops라는 새 프로젝트를 만들어 보겠습니다.
Rust 에는 loop, while, for의 세 가지 종류의 루프가 있습니다. 각각 시도해 보겠습니다.
loop 키워드는 Rust 에게 코드 블록을 영원히 또는 명시적으로 중지하라고 지시할 때까지 반복해서 실행하도록 지시합니다.
예를 들어, loops 디렉토리의 src/main.rs 파일을 다음과 같이 변경하십시오.
파일 이름: src/main.rs
fn main() {
loop {
println!("again!");
}
}
이 프로그램을 실행하면 프로그램을 수동으로 중지할 때까지 again!이 계속해서 반복적으로 출력되는 것을 볼 수 있습니다. 대부분의 터미널은 지속적인 루프에 갇힌 프로그램을 중단하기 위해 키보드 단축키 ctrl-C 를 지원합니다. 시도해 보십시오.
$ 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!
기호 ^C는 ctrl-C 를 누른 위치를 나타냅니다. 인터럽트 신호를 받았을 때 코드가 루프의 어디에 있었는지에 따라 ^C 뒤에 again!이라는 단어가 표시될 수도 있고 그렇지 않을 수도 있습니다.
다행히 Rust 는 코드 사용을 통해 루프에서 벗어나는 방법도 제공합니다. break 키워드를 루프 안에 배치하여 프로그램에 루프 실행을 중지할 시점을 알릴 수 있습니다. "정답 추측 후 종료"에서 사용자가 정답을 맞춰 게임에서 이겼을 때 프로그램을 종료하기 위해 이 작업을 수행했음을 기억하십시오.
또한 추측 게임에서 continue를 사용했는데, 루프에서 이 키워드는 프로그램에 루프의 이 반복에서 남은 코드를 건너뛰고 다음 반복으로 이동하도록 지시합니다.
loop의 용도 중 하나는 스레드가 작업을 완료했는지 확인하는 것과 같이 실패할 수 있는 작업을 다시 시도하는 것입니다. 또한 해당 작업의 결과를 루프 밖으로 나머지 코드에 전달해야 할 수도 있습니다. 이렇게 하려면 루프를 중지하는 데 사용하는 break 표현식 뒤에 반환하려는 값을 추가할 수 있습니다. 이 값은 루프 밖으로 반환되므로 다음 예제와 같이 사용할 수 있습니다.
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
루프 전에 counter라는 변수를 선언하고 0으로 초기화합니다. 그런 다음 루프에서 반환된 값을 저장하기 위해 result라는 변수를 선언합니다. 루프의 각 반복에서 counter 변수에 1을 더한 다음 counter가 10과 같은지 확인합니다. 같으면 값 counter * 2와 함께 break 키워드를 사용합니다. 루프 후에는 세미콜론을 사용하여 result에 값을 할당하는 문장을 종료합니다. 마지막으로, 이 경우 20인 result의 값을 출력합니다.
루프 안에 루프가 있는 경우 break 및 continue는 해당 시점에서 가장 안쪽 루프에 적용됩니다. 선택적으로 break 또는 continue와 함께 사용하여 해당 키워드가 가장 안쪽 루프 대신 레이블이 지정된 루프에 적용되도록 지정할 수 있는 루프에 루프 레이블을 지정할 수 있습니다. 루프 레이블은 작은 따옴표로 시작해야 합니다. 다음은 두 개의 중첩된 루프가 있는 예입니다.
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}");
}
바깥쪽 루프에는 'counting_up 레이블이 있으며 0 에서 2 까지 카운트합니다. 레이블이 없는 안쪽 루프는 10 에서 9 까지 카운트다운합니다. 레이블을 지정하지 않는 첫 번째 break는 안쪽 루프만 종료합니다. break 'counting_up; 문은 바깥쪽 루프를 종료합니다. 이 코드는 다음을 출력합니다.
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
while을 사용한 조건부 루프프로그램은 종종 루프 내에서 조건을 평가해야 합니다. 조건이 true인 동안 루프가 실행됩니다. 조건이 더 이상 true가 아니면 프로그램은 break를 호출하여 루프를 중지합니다. loop, if, else, break의 조합을 사용하여 이와 같은 동작을 구현할 수 있습니다. 원한다면 지금 프로그램에서 시도해 볼 수 있습니다. 그러나 이 패턴은 매우 일반적이므로 Rust 에는 while 루프라고 하는 내장 언어 구성 요소가 있습니다. 목록 3-3 에서 while을 사용하여 프로그램을 세 번 루프하고 매번 카운트다운한 다음, 루프 후에 메시지를 출력하고 종료합니다.
파일 이름: src/main.rs
fn main() {
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}
목록 3-3: 조건이 true로 평가되는 동안 코드를 실행하기 위해 while 루프 사용
이 구성 요소는 loop, if, else, break를 사용하는 경우 필요할 수 있는 많은 중첩을 제거하고 더 명확합니다. 조건이 true로 평가되는 동안 코드가 실행되고, 그렇지 않으면 루프를 종료합니다.
for를 사용하여 컬렉션 반복배열과 같은 컬렉션의 요소를 반복하기 위해 while 구문을 사용할 수 있습니다. 예를 들어, 목록 3-4 의 루프는 배열 a의 각 요소를 출력합니다.
파일 이름: src/main.rs
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index += 1;
}
}
목록 3-4: while 루프를 사용하여 컬렉션의 각 요소 반복
여기서 코드는 배열의 요소를 통해 카운트합니다. 인덱스 0에서 시작하여 배열의 마지막 인덱스에 도달할 때까지 루프합니다 (즉, index < 5가 더 이상 true가 아닐 때). 이 코드를 실행하면 배열의 모든 요소가 출력됩니다.
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50
예상대로 다섯 개의 모든 배열 값이 터미널에 나타납니다. index가 어느 시점에서 5의 값에 도달하더라도 루프는 배열에서 여섯 번째 값을 가져오려고 시도하기 전에 실행을 중지합니다.
그러나 이 접근 방식은 오류가 발생하기 쉽습니다. 인덱스 값 또는 테스트 조건이 잘못된 경우 프로그램이 패닉을 일으킬 수 있습니다. 예를 들어, a 배열의 정의를 네 개의 요소를 갖도록 변경했지만 조건을 while index < 4로 업데이트하는 것을 잊어버린 경우 코드는 패닉을 일으킬 것입니다. 또한 컴파일러가 루프를 통해 반복할 때마다 인덱스가 배열의 경계 내에 있는지 여부에 대한 조건부 검사를 수행하기 위해 런타임 코드를 추가하므로 속도가 느립니다.
더 간결한 대안으로 for 루프를 사용하여 컬렉션의 각 항목에 대해 일부 코드를 실행할 수 있습니다. for 루프는 목록 3-5 의 코드와 같습니다.
파일 이름: src/main.rs
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
목록 3-5: for 루프를 사용하여 컬렉션의 각 요소 반복
이 코드를 실행하면 목록 3-4 와 동일한 출력을 볼 수 있습니다. 더 중요한 것은 이제 코드의 안전성을 높이고 배열의 끝을 벗어나거나 충분히 멀리 가지 않아 일부 항목을 놓치는 것으로 인해 발생할 수 있는 버그의 가능성을 제거했다는 것입니다.
for 루프를 사용하면 목록 3-4 에서 사용된 방법과 같이 배열의 값 수를 변경하더라도 다른 코드를 변경하는 것을 기억할 필요가 없습니다.
for 루프의 안전성과 간결함은 Rust 에서 가장 일반적으로 사용되는 루프 구성 요소로 만듭니다. 목록 3-3 에서 while 루프를 사용한 카운트다운 예제와 같이 특정 횟수만큼 일부 코드를 실행하려는 경우에도 대부분의 Rustaceans 는 for 루프를 사용합니다. 이를 수행하는 방법은 표준 라이브러리에서 제공하는 Range를 사용하는 것입니다. Range는 한 숫자에서 시작하여 다른 숫자 전에 끝나는 일련의 모든 숫자를 생성합니다.
다음은 for 루프와 아직 이야기하지 않은 다른 메서드인 rev를 사용하여 범위를 반전시킨 카운트다운의 모습입니다.
파일 이름: src/main.rs
fn main() {
for number in (1..4).rev() {
println!("{number}!");
}
println!("LIFTOFF!!!");
}
이 코드가 좀 더 좋지 않나요?
축하합니다! 제어 흐름 (Control Flow) 랩을 완료했습니다. LabEx 에서 더 많은 랩을 연습하여 실력을 향상시킬 수 있습니다.