Rust의 조건문(if/else), 반복문(for/while/loop), 패턴매칭(match/if let)을 실전 예제와 함께 완벽하게 마스터하여 효율적인 흐름 제어 프로그래밍을 구현해보세요.
들어가며
Rust 프로그래밍에서 흐름 제어는 프로그램의 논리적 구조를 결정하는 핵심 요소입니다.
이전 글 "[Rust입문 #2] Rust 첫 프로그램부터 변수, 데이터 타입, 함수 완전정복"에서 기본기를 다졌다면,
[Rust입문 #2] Rust 첫 프로그램부터 변수, 데이터 타입, 함수 완전정복
Rust Hello World 프로그램 작성부터 변수 선언, 데이터 타입, 함수 사용법까지 초보자도 쉽게 따라할 수 있는 실전 가이드를 제공합니다.이전 글 Rust란 무엇인가? 특징, 장점, 설치와 개발환경 완벽
notavoid.tistory.com
이번 글에서는 Rust 조건문, Rust 반복문, Rust match문을 통해 더욱 정교한 프로그램을 작성하는 방법을 배워보겠습니다.
특히 Rust의 독특한 패턴매칭 시스템은 다른 언어에서 경험할 수 없는 강력한 기능을 제공합니다.
Rust 조건문 완전 마스터하기
if 조건문의 기본 구조
Rust 조건문은 다른 언어와 유사하지만, 몇 가지 중요한 차이점이 있습니다.
가장 큰 특징은 조건식에 괄호가 필요 없다는 점입니다.
fn main() {
let age = 25;
if age >= 18 {
println!("성인입니다.");
} else {
println!("미성년자입니다.");
}
}
실행 결과:
성인입니다.
else if를 활용한 다중 조건 처리
복잡한 조건 분기는 else if
를 사용하여 처리할 수 있습니다:
fn grade_calculator(score: i32) -> &'static str {
if score >= 90 {
"A"
} else if score >= 80 {
"B"
} else if score >= 70 {
"C"
} else if score >= 60 {
"D"
} else {
"F"
}
}
fn main() {
println!("{}", grade_calculator(95));
println!("{}", grade_calculator(85));
println!("{}", grade_calculator(75));
println!("{}", grade_calculator(55));
}
실행 결과:
A
B
C
F
조건문을 표현식으로 사용하기
Rust에서 if는 표현식이므로 값을 반환할 수 있습니다:
fn main() {
let temperature = 25;
let weather_message = if temperature > 30 {
"더워요"
} else if temperature < 10 {
"추워요"
} else {
"적당해요"
};
println!("날씨: {}", weather_message);
}
실행 결과:
날씨: 적당해요
이는 삼항 연산자가 없는 Rust에서 매우 유용한 패턴입니다.
Rust 반복문 완전 정복
for 반복문으로 컬렉션 순회하기
Rust 반복문 중 가장 안전하고 많이 사용되는 것은 for
반복문입니다.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 값으로 순회
for number in numbers.iter() {
println!("숫자: {}", number);
}
// 인덱스와 함께 순회
for (index, value) in numbers.iter().enumerate() {
println!("인덱스 {}: 값 {}", index, value);
}
// 범위 순회
for i in 0..3 {
println!("카운트: {}", i);
}
}
실행 결과:
숫자: 1
숫자: 2
숫자: 3
숫자: 4
숫자: 5
인덱스 0: 값 1
인덱스 1: 값 2
인덱스 2: 값 3
인덱스 3: 값 4
인덱스 4: 값 5
카운트: 0
카운트: 1
카운트: 2
while 반복문으로 조건부 반복 구현
조건이 참인 동안 반복을 수행할 때는 while
반복문을 사용합니다:
fn main() {
let mut counter = 0;
while counter < 3 {
println!("카운터: {}", counter);
counter += 1;
}
// 벡터에서 요소 제거하며 반복
let mut stack = vec![1, 2, 3];
while let Some(value) = stack.pop() {
println!("스택에서 제거: {}", value);
}
}
실행 결과:
카운터: 0
카운터: 1
카운터: 2
스택에서 제거: 3
스택에서 제거: 2
스택에서 제거: 1
loop 반복문과 break, continue
무한 반복이나 복잡한 탈출 조건이 필요할 때는 loop
를 사용합니다:
fn main() {
let mut attempts = 0;
loop {
attempts += 1;
if attempts == 3 {
continue; // 3번째 시도는 건너뛰기
}
if attempts > 5 {
break; // 5번 초과 시 종료
}
println!("시도 횟수: {}", attempts);
}
// 라벨을 사용한 중첩 반복문 제어
'outer: loop {
println!("외부 반복문 시작");
loop {
println!("내부 반복문");
break 'outer; // 외부 반복문까지 종료
}
println!("이 줄은 실행되지 않습니다");
}
println!("프로그램 종료");
}
실행 결과:
시도 횟수: 1
시도 횟수: 2
시도 횟수: 4
시도 횟수: 5
외부 반복문 시작
내부 반복문
프로그램 종료
Rust match문과 패턴매칭의 힘
match 기본 문법과 활용
Rust match문은 강력한 패턴매칭 기능을 제공합니다.
모든 경우를 다뤄야 하는 완전성(exhaustiveness) 검사가 특징입니다.
enum Direction {
North,
South,
East,
West,
}
fn move_player(direction: Direction) -> String {
match direction {
Direction::North => "북쪽으로 이동".to_string(),
Direction::South => "남쪽으로 이동".to_string(),
Direction::East => "동쪽으로 이동".to_string(),
Direction::West => "서쪽으로 이동".to_string(),
}
}
fn main() {
println!("{}", move_player(Direction::North));
println!("{}", move_player(Direction::East));
println!("{}", move_player(Direction::South));
}
실행 결과:
북쪽으로 이동
동쪽으로 이동
남쪽으로 이동
값 추출과 가드를 활용한 고급 패턴매칭
fn analyze_number(x: i32) -> String {
match x {
0 => "영".to_string(),
1..=10 => "한 자리 수".to_string(),
11..=99 => "두 자리 수".to_string(),
n if n < 0 => "음수".to_string(),
n if n > 999 => "천 이상".to_string(),
_ => "세 자리 수".to_string(),
}
}
// Option과 Result 처리
fn safe_divide(a: i32, b: i32) -> Option<i32> {
match b {
0 => None,
_ => Some(a / b),
}
}
fn handle_division_result(result: Option<i32>) {
match result {
Some(value) => println!("결과: {}", value),
None => println!("0으로 나눌 수 없습니다."),
}
}
fn main() {
println!("{}", analyze_number(0));
println!("{}", analyze_number(5));
println!("{}", analyze_number(25));
println!("{}", analyze_number(-10));
println!("{}", analyze_number(1500));
handle_division_result(safe_divide(10, 2));
handle_division_result(safe_divide(10, 0));
}
실행 결과:
영
한 자리 수
두 자리 수
음수
천 이상
결과: 5
0으로 나눌 수 없습니다.
if let으로 간단한 패턴매칭 구현
단일 패턴만 확인할 때는 if let을 사용하면 더 간결합니다:
fn main() {
let some_option = Some(5);
let none_option: Option<i32> = None;
// match 사용
match some_option {
Some(value) => println!("match로 확인한 값: {}", value),
None => println!("값이 없습니다"),
}
// if let 사용 (더 간결)
if let Some(value) = some_option {
println!("if let으로 확인한 값: {}", value);
}
// None인 경우 테스트
if let Some(value) = none_option {
println!("값: {}", value);
} else {
println!("none_option에는 값이 없습니다");
}
// 벡터에서 첫 번째 요소 추출
let vec = vec![1, 2, 3];
if let Some(first) = vec.first() {
println!("첫 번째 요소: {}", first);
}
}
실행 결과:
match로 확인한 값: 5
if let으로 확인한 값: 5
none_option에는 값이 없습니다
첫 번째 요소: 1
흐름 제어 패턴 비교표
구조 | 용도 | 특징 | 성능 |
---|---|---|---|
if/else | 조건부 분기 | 표현식으로 사용 가능 | 빠름 |
match | 패턴매칭 | 완전성 검사, 값 추출 | 컴파일 시 최적화 |
if let | 단일 패턴 매칭 | 간결한 문법 | 빠름 |
for | 컬렉션 순회 | 안전한 반복 | 빠름 |
while | 조건부 반복 | 조건 기반 반복 | 보통 |
loop | 무한 반복 | 명시적 종료 | 빠름 |
실전 예제: 간단한 계산기 구현
지금까지 배운 흐름 제어 개념들을 활용해 간단한 계산기를 만들어보겠습니다:
enum Operation {
Add,
Subtract,
Multiply,
Divide,
}
fn calculate(a: f64, b: f64, op: Operation) -> Result<f64, String> {
match op {
Operation::Add => Ok(a + b),
Operation::Subtract => Ok(a - b),
Operation::Multiply => Ok(a * b),
Operation::Divide => {
if b == 0.0 {
Err("0으로 나눌 수 없습니다".to_string())
} else {
Ok(a / b)
}
}
}
}
fn main() {
let operations = vec![
(10.0, 5.0, Operation::Add),
(10.0, 5.0, Operation::Subtract),
(10.0, 5.0, Operation::Multiply),
(10.0, 0.0, Operation::Divide),
];
for (a, b, op) in operations {
let op_name = match op {
Operation::Add => "덧셈",
Operation::Subtract => "뺄셈",
Operation::Multiply => "곱셈",
Operation::Divide => "나눗셈",
};
match calculate(a, b, op) {
Ok(result) => println!("{} 연산 결과: {}", op_name, result),
Err(error) => println!("에러: {}", error),
}
}
}
실행 결과:
덧셈 연산 결과: 15
뺄셈 연산 결과: 5
곱셈 연산 결과: 50
에러: 0으로 나눌 수 없습니다
성능 최적화 팁
1. match vs if/else 체인 성능 비교
// 효율적인 match 사용
fn efficient_grade(score: i32) -> &'static str {
match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F",
}
}
// 비효율적인 if/else 체인
fn inefficient_grade(score: i32) -> &'static str {
if score >= 90 && score <= 100 {
"A"
} else if score >= 80 && score < 90 {
"B"
} else if score >= 70 && score < 80 {
"C"
} else if score >= 60 && score < 70 {
"D"
} else {
"F"
}
}
fn main() {
println!("match 사용: {}", efficient_grade(85));
println!("if/else 사용: {}", inefficient_grade(85));
println!("match 사용: {}", efficient_grade(95));
println!("if/else 사용: {}", inefficient_grade(95));
}
실행 결과:
match 사용: B
if/else 사용: B
match 사용: A
if/else 사용: A
2. 반복문 최적화
// 효율적인 반복문 사용
fn sum_efficient(numbers: &[i32]) -> i32 {
numbers.iter().sum() // 내장 함수 활용
}
// 일반적인 반복문
fn sum_normal(numbers: &[i32]) -> i32 {
let mut total = 0;
for &number in numbers {
total += number;
}
total
}
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
println!("효율적인 합계: {}", sum_efficient(&numbers));
println!("일반적인 합계: {}", sum_normal(&numbers));
}
실행 결과:
효율적인 합계: 15
일반적인 합계: 15
디버깅과 테스트 방법
조건문 디버깅
fn debug_condition(value: i32) {
println!("입력값: {}", value);
let result = if value > 0 {
println!("양수 분기 실행");
"positive"
} else if value < 0 {
println!("음수 분기 실행");
"negative"
} else {
println!("0 분기 실행");
"zero"
};
println!("결과: {}", result);
}
fn main() {
debug_condition(5);
debug_condition(-3);
debug_condition(0);
}
실행 결과:
입력값: 5
양수 분기 실행
결과: positive
입력값: -3
음수 분기 실행
결과: negative
입력값: 0
0 분기 실행
결과: zero
단위 테스트 작성
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_grade_calculator() {
assert_eq!(grade_calculator(95), "A");
assert_eq!(grade_calculator(85), "B");
assert_eq!(grade_calculator(75), "C");
assert_eq!(grade_calculator(65), "D");
assert_eq!(grade_calculator(55), "F");
}
#[test]
fn test_safe_divide() {
assert_eq!(safe_divide(10, 2), Some(5));
assert_eq!(safe_divide(10, 0), None);
}
}
테스트 실행 결과:
running 2 tests
test tests::test_grade_calculator ... ok
test tests::test_safe_divide ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
고급 패턴매칭 기법
구조체와 열거형 분해
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
#[derive(Debug)]
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn handle_message(msg: Message) {
match msg {
Message::Quit => println!("프로그램 종료"),
Message::Move { x, y } => println!("이동: ({}, {})", x, y),
Message::Write(text) => println!("메시지: {}", text),
Message::ChangeColor(r, g, b) => {
println!("색상 변경: RGB({}, {}, {})", r, g, b)
}
}
}
fn analyze_point(point: Point) {
match point {
Point { x: 0, y: 0 } => println!("원점"),
Point { x: 0, y } => println!("Y축 위의 점: (0, {})", y),
Point { x, y: 0 } => println!("X축 위의 점: ({}, 0)", x),
Point { x, y } if x == y => println!("대각선 위의 점: ({}, {})", x, y),
Point { x, y } => println!("일반 점: ({}, {})", x, y),
}
}
fn main() {
handle_message(Message::Move { x: 10, y: 20 });
handle_message(Message::Write("안녕하세요".to_string()));
handle_message(Message::ChangeColor(255, 0, 0));
analyze_point(Point { x: 0, y: 0 });
analyze_point(Point { x: 0, y: 5 });
analyze_point(Point { x: 3, y: 0 });
analyze_point(Point { x: 4, y: 4 });
analyze_point(Point { x: 2, y: 3 });
}
실행 결과:
이동: (10, 20)
메시지: 안녕하세요
색상 변경: RGB(255, 0, 0)
원점
Y축 위의 점: (0, 5)
X축 위의 점: (3, 0)
대각선 위의 점: (4, 4)
일반 점: (2, 3)
참조와 소유권을 고려한 패턴매칭
fn process_option_string(opt: &Option<String>) {
match opt {
Some(s) => println!("문자열 길이: {}", s.len()),
None => println!("값이 없습니다"),
}
}
fn take_ownership(opt: Option<String>) {
match opt {
Some(s) => println!("소유권 이전: {}", s),
None => println!("값이 없습니다"),
}
}
fn main() {
let some_string = Some("Hello Rust".to_string());
let none_string: Option<String> = None;
// 참조로 처리
process_option_string(&some_string);
process_option_string(&none_string);
// 소유권 이전
take_ownership(some_string);
take_ownership(none_string);
}
실행 결과:
문자열 길이: 10
값이 없습니다
소유권 이전: Hello Rust
값이 없습니다
실무에서 자주 사용하는 패턴들
1. 에러 처리 패턴
use std::fs::File;
use std::io::ErrorKind;
fn open_file_with_handling(filename: &str) {
let file_result = File::open(filename);
match file_result {
Ok(file) => println!("파일 열기 성공"),
Err(error) => match error.kind() {
ErrorKind::NotFound => {
println!("파일을 찾을 수 없습니다: {}", filename);
}
ErrorKind::PermissionDenied => {
println!("파일 접근 권한이 없습니다: {}", filename);
}
other_error => {
println!("파일을 열 수 없습니다: {:?}", other_error);
}
}
}
}
fn main() {
open_file_with_handling("존재하지않는파일.txt");
}
실행 결과:
파일을 찾을 수 없습니다: 존재하지않는파일.txt
2. 설정 값 처리 패턴
enum LogLevel {
Debug,
Info,
Warning,
Error,
}
struct Config {
log_level: LogLevel,
max_connections: Option<usize>,
database_url: Option<String>,
}
fn configure_application(config: Config) {
// 로그 레벨 설정
match config.log_level {
LogLevel::Debug => println!("디버그 모드로 실행"),
LogLevel::Info => println!("정보 수준 로깅 활성화"),
LogLevel::Warning => println!("경고 수준 로깅 활성화"),
LogLevel::Error => println!("에러 수준 로깅만 활성화"),
}
// 최대 연결 수 설정
if let Some(max_conn) = config.max_connections {
println!("최대 연결 수: {}", max_conn);
} else {
println!("기본 연결 수 사용: 100");
}
// 데이터베이스 URL 설정
match config.database_url {
Some(url) => println!("데이터베이스 연결: {}", url),
None => println!("인메모리 데이터베이스 사용"),
}
}
fn main() {
let config = Config {
log_level: LogLevel::Info,
max_connections: Some(50),
database_url: None,
};
configure_application(config);
}
실행 결과:
정보 수준 로깅 활성화
최대 연결 수: 50
인메모리 데이터베이스 사용
관련 참고 자료
Rust의 흐름 제어를 더 깊이 이해하기 위해 다음 공식 문서들을 참고하세요:
마무리
이번 글에서는 Rust 조건문, Rust 반복문, Rust match문의 기본부터 고급 활용법까지 살펴봤습니다.
흐름 제어는 프로그래밍의 핵심이며, Rust의 강력한 타입 시스템과 결합되어 안전하고 효율적인 코드를 작성할 수 있게 해줍니다.
특히 match와 if let을 통한 패턴매칭은 Rust만의 독특한 장점이니 꼭 익혀두시기 바랍니다.
핵심 포인트를 정리하면:
- 조건문은 표현식으로 사용하여 값을 반환할 수 있습니다
- 반복문은 안전성을 위해 for 문을 우선적으로 사용하세요
- match 문은 완전성 검사로 모든 경우를 처리해야 합니다
- if let은 단일 패턴 매칭에 유용합니다
- 패턴매칭은 에러 처리와 설정 관리에 매우 효과적입니다
다음 글 "Rust 소유권과 참조, 에러 처리 완벽 이해하기"에서는 Rust의 핵심 개념인 소유권 시스템을 자세히 다뤄보겠습니다.
[Rust입문 #4] Rust 소유권과 참조, 에러 처리 완벽 이해하기
Rust의 소유권(ownership), 참조(reference), 에러 처리는 메모리 안전성을 보장하는 핵심 개념으로,이 가이드에서는 실무에서 바로 활용할 수 있는 실전 예제와 함께 완벽하게 마스터할 수 있는 방법을
notavoid.tistory.com
지금까지 배운 기본기를 바탕으로 더욱 안전하고 효율적인 Rust 프로그래밍을 경험해보세요!
강의추천
세상에서 제일 쉬운 러스트 프로그래밍 강의 | 윤인도 - 인프런
윤인도 | 이 강의를 통해 여러분은 가장 핫한 언어, Rust를 활용하실 수 있게 됩니다. 파이썬의 단점인 GIL을 극복하고 빠르게 동작하는 코드를 만들 수 있습니다., 최근 3년, 가장 핫한 언어 러스트
www.inflearn.com
'프로그래밍 언어 실전 가이드' 카테고리의 다른 글
[Rust입문 #5] Rust 구조체, 열거형, 컬렉션 완전 정리 (0) | 2025.07.11 |
---|---|
[Rust입문 #4] Rust 소유권과 참조, 에러 처리 완벽 이해하기 (0) | 2025.07.08 |
[Rust입문 #2] Rust 첫 프로그램부터 변수, 데이터 타입, 함수 완전정복 (0) | 2025.07.07 |
[Rust입문 #1] Rust란 무엇인가? 특징, 장점, 설치와 개발환경 완벽 가이드 (0) | 2025.07.07 |
루아 입문 시리즈 #20: 루아 생태계와 미래 (0) | 2025.07.06 |