본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2026. 3. 6. · 2 Views
Rust 표현식과 구문 완벽 가이드
Rust에서 표현식과 구문의 차이를 명확히 이해하고, 이를 활용해 더 간결하고 안전한 코드를 작성하는 방법을 배워봅니다. 입문자가 가장 헷갈려하는 개념을 실무 예제와 함께 쉽게 풀어냅니다.
목차
1. 표현식과 구문의 기본 개념
어느 날 김개발 씨가 Rust 코드를 작성하다가 이상한 컴파일 에러를 만났습니다. 분명히 다른 언어에서는 잘 작동하던 코드인데 Rust에서만 에러가 발생했습니다.
선배에게 물어보니 "Rust는 표현식 기반 언어예요"라는 답변을 받았습니다. 도대체 표현식이 뭘까요?
표현식은 값을 반환하는 코드 조각이고, 구문은 값을 반환하지 않고 어떤 동작을 수행하는 코드입니다. Rust에서는 거의 모든 것이 표현식이어서, if문이나 블록도 값을 반환할 수 있습니다.
이를 이해하면 더 간결하고 안전한 코드를 작성할 수 있습니다.
다음 코드를 살펴봅시다.
fn main() {
// 구문(Statement): 값을 반환하지 않음
let x = 5; // let 구문
// 표현식(Expression): 값을 반환함
let y = {
let a = 3;
let b = 4;
a + b // 세미콜론 없이 - 이것이 표현식!
};
println!("x: {}, y: {}", x, y); // x: 5, y: 7
// 잘못된 예: 세미콜론을 붙이면 구문이 됨
let z = {
let a = 3;
let b = 4;
a + b; // 세미콜론 추가 - 이제 구문이 됨
}; // 에러! ()를 반환
}
김개발 씨는 입사 6개월 차 주니어 개발자입니다. Python과 JavaScript를 주로 사용하다가 최근 Rust 프로젝트에 투입되었습니다.
첫날부터 당황스러운 일이 발생했습니다. 분명히 논리적으로 맞는 코드인데 컴파일러가 계속 에러를 뿜어냈습니다.
"expected type, found ()"라는 이상한 에러 메시지가 반복해서 나타났습니다. 선배 개발자 박러스트 씨가 다가와 화면을 살펴봅니다.
"아, 여기가 문제네요. 세미콜론 하나 때문이에요." 김개발 씨는 고개를 갸웃거렸습니다.
세미콜론이 그렇게 중요한가요? Rust에서 표현식과 구문의 차이를 이해하는 것은 매우 중요합니다.
쉽게 비유하자면, 표현식은 마치 계산기에 숫자를 입력했을 때 결과값이 나오는 것과 같습니다. 반면 구문은 계산기의 "지우기" 버튼을 누르는 것과 같아요.
무언가 동작은 하지만 결과값은 나오지 않습니다. 다른 언어에서는 이 구분이 그리 중요하지 않았습니다.
하지만 Rust에서는 철학이 다릅니다. Rust는 거의 모든 것이 표현식입니다.
숫자, 문자열, 함수 호출, 블록, 심지어 if문까지도 표현식입니다. 그렇다면 구문은 무엇일까요?
Rust에서 진정한 의미의 구문은 let 변수 선언, 함수 정의, extern 블록 등 매우 제한적입니다. 대부분의 코드는 표현식으로 처리됩니다.
이 차이를 명확히 이해하려면 세미콜론의 역할을 알아야 합니다. Rust에서 세미콜론은 "이 코드는 값을 반환하지 않는다"고 선언하는 것과 같습니다.
세미콜론을 붙이면 표현식이 구문이 되어버립니다. 코드를 다시 살펴봅시다.
y 변수를 선언할 때 블록 마지막에 a + b라고 작성했습니다. 세미콜론이 없죠?
이것이 핵심입니다. 세미콜론이 없으면 이 코드는 표현식이 되어 7이라는 값을 반환합니다.
반면 z 변수를 선언할 때는 a + b;라고 작성했습니다. 세미콜론이 붙어있네요.
이제 이 코드는 구문이 되어 ()라는 빈 값을 반환합니다. 이것이 에러의 원인이었습니다.
실무에서 이 특성을 활용하면 매우 간결한 코드를 작성할 수 있습니다. 예를 들어 조건에 따라 다른 값을 할당할 때, 삼항 연산자 대신 if 표현식을 사용할 수 있습니다.
주의할 점도 있습니다. 다른 언어에서 넘어온 개발자들은 무의식적으로 모든 줄에 세미콜론을 붙이는 습관이 있습니다.
Rust에서는 이 습관이 문제가 될 수 있습니다. 의도치 않게 표현식을 구문으로 바꾸어버리기 때문입니다.
김개발 씨는 박러스트 씨의 설명을 듣고 무릎을 쳤습니다. "아, 그래서 Rust 코드를 보면 세미콜론이 없는 줄이 많았군요!" 오늘 배운 내용을 정리해봅시다.
표현식은 값을 반환하고, 구문은 반환하지 않습니다. 세미콜론의 유무가 이 둘을 결정합니다.
이 개념을 명확히 이해하면 Rust의 독특한 문법을 자연스럽게 받아들일 수 있습니다.
실전 팁
💡 - 블록의 마지막 표현식에는 세미콜론을 붙이지 마세요
- 함수에서 값을 반환할 때도 return 키워드 대신 세미콜론 없는 표현식을 사용하세요
- 컴파일 에러가 "expected type, found ()"라면 세미콜론을 확인해보세요
2. if 표현식의 활용
김개발 씨가 사용자 등급에 따라 다른 할인율을 적용하는 함수를 작성하고 있습니다. Python에서는 if-elif-else를 사용했는데, Rust에서도 비슷하게 작성하려니 변수 선언이 길어지고 코드가 지저분해졌습니다.
더 깔끔한 방법이 없을까요?
Rust에서 if는 표현식입니다. 따라서 if문 자체가 값을 반환할 수 있고, 변수에 직접 할당할 수 있습니다.
삼항 연산자가 필요 없는 이유가 여기에 있습니다. if 표현식을 활용하면 더 간결하고 가독성 높은 코드를 작성할 수 있습니다.
다음 코드를 살펴봅시다.
fn main() {
let score = 85;
// if 표현식을 변수에 직접 할당
let grade = if score >= 90 {
"A"
} else if score >= 80 {
"B"
} else if score >= 70 {
"C"
} else {
"F"
};
println!("학점: {}", grade); // 학점: B
// 실무 활용: 할인율 계산
let is_member = true;
let purchase_amount = 150000;
let discount_rate = if purchase_amount >= 100000 && is_member {
0.2 // 20% 할인
} else if purchase_amount >= 100000 {
0.1 // 10% 할인
} else if is_member {
0.05 // 5% 할인
} else {
0.0 // 할인 없음
};
let final_price = (purchase_amount as f64) * (1.0 - discount_rate);
println!("최종 가격: {}원", final_price as i32);
}
쇼핑몰 회사에서 일하는 김개발 씨는 회원 등급별 할인 시스템을 개발하고 있습니다. 기존에 사용하던 Python에서는 변수를 먼저 선언하고 if문에서 값을 변경하는 방식을 사용했습니다.
하지만 Rust에서 똑같이 작성하려니 문제가 있었습니다. 변수를 let으로 선언하면 기본적으로 변경할 수 없으니까요.
mut을 붙여서 가변 변수로 만들 수도 있지만, 그러면 코드가 지저분해집니다. 박러스트 씨가 옆에서 조언했습니다.
"if문을 표현식으로 활용해보세요. 훨씬 깔끔해질 거예요." Rust의 if는 다른 언어와 결정적으로 다릅니다.
if 자체가 표현식이라는 점입니다. 쉽게 비유하자면, if문이 마치 자판기와 같습니다.
동전을 넣고 버튼을 누르면 음료수가 나오듯이, 조건을 넣으면 그에 맞는 값이 나옵니다. 전통적인 언어에서는 if문이 단순한 제어 구조였습니다.
조건에 따라 다른 코드를 실행할 뿐이죠. 하지만 Rust에서는 if문이 값을 생산해냅니다.
이것이 왜 중요할까요? 코드를 작성할 때마다 변수를 선언하고, 조건을 검사하고, 변수를 수정하는 패턴이 반복됩니다.
이런 코드는 읽기도 어렵고 실수하기도 쉽습니다. 특히 변수를 여러 번 수정하면 코드를 읽는 사람이 "이 변수가 언제, 어떻게 바뀌지?"라고 계속 추적해야 합니다.
if 표현식을 사용하면 이 문제가 해결됩니다. 변수가 한 번만 선언되고, 그 값이 if 표현식에 의해 결정됩니다.
코드를 읽는 사람은 "이 변수의 값은 여기서 결정되는구나"라고 한눈에 파악할 수 있습니다. 위의 코드를 살펴봅시다.
score 변수에 따라 grade를 결정하는 부분입니다. if 표현식 전체가 하나의 값으로 평가되어 grade 변수에 할당됩니다.
마치 수학 공식처럼 깔끔합니다. 실무 예제인 할인율 계산도 마찬가지입니다.
구매 금액과 회원 여부에 따라 할인율이 결정됩니다. 복잡한 비즈니스 로직이 if 표현식 하나로 깔끔하게 정리되었습니다.
주의할 점이 있습니다. if 표현식의 모든 분기는 반드시 같은 타입을 반환해야 합니다.
한 분기에서는 문자열을 반환하고 다른 분기에서는 숫자를 반환하면 컴파일 에러가 발생합니다. 이것은 Rust의 타입 시스템이 코드의 안전성을 보장하는 방식입니다.
김개발 씨는 이 특성을 활용해 할인율 계산 로직을 한 줄로 깔끔하게 정리했습니다. "와, Python에서는 10줄이 넘었는데 Rust에서는 5줄로 끝났어요!" if 표현식을 마스터하면 코드의 가독성이 크게 향상됩니다.
불필요한 가변 변수를 줄이고, 로직을 명확하게 표현할 수 있습니다. 이것이 Rust가 지향하는 안전하고 표현력 있는 코드입니다.
실전 팁
💡 - if 표현식의 모든 분기는 같은 타입을 반환해야 합니다
- 복잡한 조건문은 if 표현식보다 match 표현식이 더 깔끔할 수 있습니다
- if 표현식 뒤에는 세미콜론을 붙여서 let 구문을 완성하세요
3. 블록 표현식과 스코프
김개발 씨가 복잡한 계산 로직을 작성하던 중, 중간 계산을 위한 임시 변수들이 너무 많아졌습니다. 함수 전체에서 이 변수들이 보이니 코드가 산만해졌습니다.
함수 안에 또 다른 스코프를 만들어서 임시 변수를 격리할 수 있을까요?
블록 표현식은 중괄호로 감싸진 코드 영역으로, 자신만의 스코프를 가지며 마지막 표현식의 값을 반환합니다. 블록을 활용하면 임시 변수의 스코프를 제한하고, 복잡한 초기화 로직을 깔끔하게 정리할 수 있습니다.
다음 코드를 살펴봅시다.
fn main() {
// 블록 표현식으로 복잡한 초기화 캡슐화
let total = {
// 이 변수들은 블록 밖에서 접근 불가
let base_price = 10000;
let tax_rate = 0.1;
let discount = 500;
let subtotal = base_price - discount;
let tax = (subtotal as f64 * tax_rate) as i32;
subtotal + tax // 이 값이 total에 할당됨
};
println!("최종 금액: {}원", total);
// base_price, tax_rate 등은 여기서 접근 불가
// 실무 예: 설정 값 검증 후 사용
let config = {
let raw_port = "8080";
let port = raw_port.parse::<u16>().expect("Invalid port");
let raw_timeout = "30";
let timeout = raw_timeout.parse::<u64>().expect("Invalid timeout");
// 검증된 설정값만 반환
(port, timeout)
};
println!("Port: {}, Timeout: {}s", config.0, config.1);
}
결제 시스템을 개발하던 김개발 씨는 고민에 빠졌습니다. 최종 결제 금액을 계산하기 위해 기본 가격, 할인율, 세금, 포인트 등 여러 단계의 계산이 필요했습니다.
모든 중간 계산을 함수 최상위에서 처리하려니 변수가 너무 많아졌습니다. 그것도 잠깐 쓰고 버릴 변수들인데 말이죠.
다른 개발자가 코드를 읽을 때 이 변수들이 어디서 왔는지, 어디까지 유효한지 파악하기 어려울 것 같았습니다. 박러스트 씨가 코드를 보더니 말했습니다.
"블록 표현식을 사용해보세요. 임시 변수를 격리할 수 있어요." 블록 표현식은 Rust의 독특한 기능입니다.
중괄호로 감싸진 코드 블록이 하나의 표현식처럼 동작합니다. 쉽게 비유하자면, 블록 표현식은 마치 작은 함수와 같습니다.
입력 없이 실행되고, 마지막에 결과값을 반환하는 함수 말이죠. 블록의 핵심은 스코프 격리입니다.
블록 안에서 선언된 변수는 블록 밖에서 접근할 수 없습니다. 이것이 왜 유용할까요?
첫째, 임시 변수가 전역 스코프를 오염시키지 않습니다. 복잡한 계산에 필요한 중간 변수들이 블록 안에서만 존재하다가 사라집니다.
코드를 읽는 사람은 "이 변수들은 이 계산에만 필요하구나"라고 명확히 알 수 있습니다. 둘째, 불변성을 유지하기 쉽습니다.
함수 전체에서 변수를 수정하는 대신, 블록 안에서만 계산하고 최종 결과만 반환할 수 있습니다. Rust가 지향하는 안전한 코드 패턴입니다.
셋째, 초기화 로직을 깔끔하게 정리할 수 있습니다. 예제의 config 변수를 보세요.
문자열을 파싱하고 검증하는 복잡한 과정이 블록 안에 캡슐화되어 있습니다. 블록 밖에서는 이미 검증된 설정값만 사용하면 됩니다.
코드를 다시 살펴봅시다. total 변수를 계산하는 블록 안에 base_price, tax_rate, discount, subtotal, tax 등의 변수가 있습니다.
이 변수들은 블록이 끝나는 순간 사라집니다. 밖에서는 total 변수만 깔끔하게 남습니다.
실무에서 이 패턴은 매우 자주 사용됩니다. 특히 데이터베이스 연결 초기화, 설정 파일 파싱, 복잡한 객체 생성 등에서 유용합니다.
모든 초기화 로직을 블록 안에 넣고, 완성된 객체만 반환하는 방식입니다. 주의할 점이 있습니다.
블록의 마지막 표현식에 세미콜론을 붙이면 안 됩니다. 붙이면 구문이 되어버려 ()가 반환됩니다.
김개발 씨도 처음에 이 실수를 해서 한참을 헤맸습니다. 김개발 씨는 블록 표현식을 사용해 결제 계산 로직을 깔끔하게 정리했습니다.
"이제 함수의 핵심 로직만 한눈에 보이네요!" 블록 표현식은 단순해 보이지만, 코드의 품질을 크게 높여줍니다. 변수의 스코프를 명확히 하고, 불필요한 노출을 줄이며, 코드를 더 읽기 쉽게 만듭니다.
실전 팁
💡 - 블록 마지막 표현식에 세미콜론을 붙이지 마세요
- 복잡한 초기화 로직은 블록으로 캡슐화하세요
- 블록 안의 변수는 블록 밖에서 접근할 수 없다는 점을 활용하세요
4. match 표현식의 힘
사용자의 요청 타입에 따라 다른 처리를 해야 하는 코드를 작성하던 김개발 씨는 if-else 문이 끝도 없이 이어지는 것을 발견했습니다. 가독성도 떨어지고, 실수로 조건을 빼먹을까 걱정이 되었습니다.
더 안전하고 깔끔한 방법이 없을까요?
match 표현식은 패턴 매칭을 통해 값을 분기 처리하는 강력한 표현식입니다. 모든 가능한 경우를 처리해야 하므로 실수를 방지할 수 있고, 패턴 매칭으로 코드가 매우 간결해집니다.
if-else 체인보다 안전하고 표현력이 뛰어납니다.
다음 코드를 살펴봅시다.
enum HttpStatus {
Ok,
NotFound,
ServerError,
Unauthorized,
}
fn main() {
let status = HttpStatus::NotFound;
// match 표현식으로 상태별 메시지 생성
let message = match status {
HttpStatus::Ok => "요청이 성공했습니다",
HttpStatus::NotFound => "리소스를 찾을 수 없습니다",
HttpStatus::ServerError => "서버 오류가 발생했습니다",
HttpStatus::Unauthorized => "인증이 필요합니다",
};
println!("{}", message);
// 실무 활용: 숫자 범위 매칭
let score = 85;
let grade = match score {
90..=100 => "A",
80..=89 => "B",
70..=79 => "C",
60..=69 => "D",
_ => "F", // 와일드카드 패턴
};
println!("학점: {}", grade);
// 값 추출과 동시에 매칭
enum Result {
Success(i32),
Error(String),
}
let result = Result::Success(42);
let output = match result {
Result::Success(value) => format!("성공: {}", value),
Result::Error(msg) => format!("실패: {}", msg),
};
println!("{}", output);
}
API 서버를 개발하던 김개발 씨는 HTTP 상태 코드에 따라 다른 응답 메시지를 반환해야 했습니다. 처음에는 if-else로 작성했습니다.
if status == 200 { ... } else if status == 404 { ...
} else if status == 500 { ... } 코드가 길어지고 가독성이 떨어졌습니다.
더 큰 문제는 새로운 상태 코드가 추가될 때마다 else if를 계속 붙여야 한다는 점이었습니다. 실수로 하나를 빼먹으면 버그가 발생합니다.
박러스트 씨가 코드를 보고 말했습니다. "이런 경우 match 표현식을 사용하세요.
훨씬 안전하고 깔끔해요." match 표현식은 Rust의 자랑거리 중 하나입니다. 쉽게 비유하자면, match는 마치 스위트샵의 진열대와 같습니다.
다양한 종류의 사탕이 정돈되어 있고, 원하는 것을 명확하게 선택할 수 있습니다. 전통적인 if-else와 결정적으로 다른 점이 있습니다.
match는 모든 가능한 경우를 처리하도록 강제합니다. 하나라도 빠뜨리면 컴파일 에러가 발생합니다.
이것이 왜 중요할까요? 소프트웨어 버그의 많은 부분이 "예상치 못한 경우"에서 발생합니다.
개발자가 A와 B만 생각했는데 사용자가 C를 입력하는 경우죠. match 표현식은 이런 실수를 컴파일 타임에 잡아냅니다.
코드를 살펴봅시다. HttpStatus enum의 모든 변형에 대해 처리가 정의되어 있습니다.
하나라도 빠뜨리면 Rust 컴파일러가 즉시 에러를 알려줍니다. "Non-exhaustive patterns"라는 메시지와 함께요.
숫자 범위 매칭 기능도 강력합니다. 90..=100 같은 문법으로 범위를 지정할 수 있습니다.
이렇게 하면 if score >= 90 && score <= 100 같은 복잡한 조건문이 필요 없습니다. 와일드카드 패턴 _는 "나머지 모든 경우"를 처리합니다.
특별히 처리해야 할 경우만 명시하고, 나머지는 와일드카드로 처리하면 코드가 간결해집니다. 가장 강력한 기능은 값 추출입니다.
enum이나 구조체 안의 값을 패턴 매칭과 동시에 추출할 수 있습니다. Result::Success(value)에서 value가 추출되는 것을 보세요.
별도의 변수 할당 없이 값에 접근할 수 있습니다. 실무에서 match는 에러 처리, 상태 관리, 메시지 파싱 등 다양한 곳에서 활용됩니다.
특히 enum과 결합하면 타입 안전한 코드를 작성할 수 있습니다. 주의할 점이 있습니다.
match의 각 arm(갈래)은 같은 타입을 반환해야 합니다. 하나는 문자열을 반환하고 다른 하나는 숫자를 반환하면 컴파일 에러가 발생합니다.
김개발 씨는 match 표현식으로 상태 처리 코드를 완전히 재작성했습니다. "이제 새로운 상태가 추가되어도 컴파일러가 알려주겠네요!" match 표현식은 Rust가 제공하는 안전성과 표현력의 정수입니다.
모든 경우를 명시적으로 처리하고, 패턴 매칭으로 코드를 간결하게 만듭니다. if-else를 남용하는 대신 match를 적극 활용하세요.
실전 팁
💡 - enum과 match를 함께 사용하면 모든 경우를 컴파일러가 검증합니다
- 와일드카드
_는 나머지 모든 경우를 처리합니다 - match의 각 arm은 같은 타입을 반환해야 합니다
5. 반복문에서의 표현식 활용
김개발 씨가 배열에서 특정 조건을 만족하는 첫 번째 요소를 찾아야 합니다. for 문을 돌면서 찾으면 될 것 같은데, 찾은 값을 어떻게 함수 전체에서 사용할 수 있을까요?
변수를 함수 최상위에 선언해야 할까요?
반복문과 표현식을 결합하면 더 깔끔한 코드를 작성할 수 있습니다. loop는 break로 값을 반환할 수 있고, for와 if를 조합해도 블록 표현식을 통해 값을 추출할 수 있습니다.
불필요한 가변 변수를 줄이는 핵심 기법입니다.
다음 코드를 살펴봅시다.
fn main() {
let numbers = vec![10, 25, 30, 45, 50];
// loop와 break 표현식으로 값 반환
let first_over_40 = loop {
let mut found = None;
for &num in &numbers {
if num > 40 {
found = Some(num);
break;
}
}
break found;
};
println!("40 초과 첫 번째: {:?}", first_over_40);
// 더 깔끔한 방법: 이터레이터와 find
let result = numbers.iter().find(|&&x| x > 40);
println!("이터레이터 방식: {:?}", result);
// loop로 인덱스 찾기
let items = vec!["apple", "banana", "cherry", "date"];
let target = "cherry";
let index = {
let mut i = 0;
loop {
if items[i] == target {
break i; // break가 값을 반환!
}
i += 1;
}
};
println!("{}의 인덱스: {}", target, index);
// 중첩 루프에서 레이블과 함께 사용
let matrix = vec![
vec![1, 2, 3],
vec![4, 5, 6],
vec![7, 8, 9],
];
let position = 'outer: loop {
for (i, row) in matrix.iter().enumerate() {
for (j, &val) in row.iter().enumerate() {
if val == 5 {
break 'outer (i, j); // 레이블로 외부 루프 탈출
}
}
}
break (-1, -1); // 찾지 못한 경우
};
println!("5의 위치: {:?}", position);
}
데이터 처리 로직을 작성하던 김개발 씨는 난관에 봉착했습니다. 대용량 배열에서 특정 조건을 만족하는 첫 번째 요소를 찾아야 했습니다.
전통적인 방식대로 하려면 가변 변수를 선언하고, for 문을 돌며 찾으면 변수에 값을 할당하고 루프를 빠져나가야 합니다. 코드가 길어지고 가변 변수를 사용해야 한다는 점이 마음에 걸렸습니다.
박러스트 씨가 말했습니다. "loop와 break를 표현식으로 활용해보세요.
가변 변수 없이 깔끔하게 작성할 수 있어요." loop 표현식은 Rust의 독특한 기능입니다. 무한 루프처럼 보이지만, break를 통해 값을 반환할 수 있습니다.
쉽게 비유하자면, loop는 마치 비상 탈출구가 있는 미로와 같습니다. 계속 돌 수 있지만 원할 때 언제든 탈출하며 무언가를 챙길 수 있습니다.
다른 언어에서는 break가 단순히 루프를 종료하는 역할만 했습니다. 하지만 Rust에서는 break 뒤에 값을 붙여서 반환할 수 있습니다.
break i라고 작성하면 i 값이 loop 전체의 반환값이 됩니다. 이것이 왜 유용할까요?
첫째, 가변 변수를 줄일 수 있습니다. found 같은 변수를 별도로 선언할 필요 없이, break로 바로 값을 반환하면 됩니다.
둘째, 코드의 의도가 명확해집니다. "이 조건에서 이 값을 반환한다"는 의도가 break 문에 직접 표현됩니다.
코드를 살펴봅시다. target의 인덱스를 찾는 부분에서 loop를 사용했습니다.
items[i] == target 조건이 만족되면 break i로 인덱스를 반환합니다. 별도의 변수 없이 깔끔하게 처리됩니다.
중첩 루프에서는 레이블을 활용할 수 있습니다. 'outer라고 레이블을 붙이면 break 'outer로 해당 루프까지 한 번에 탈출할 수 있습니다.
이때도 값을 반환할 수 있습니다. 2차원 배열에서 특정 값의 위치를 찾는 예제에서 이 기능이 활용되었습니다.
실무에서는 이 패턴이 매우 자주 사용됩니다. 특히 파싱, 검색, 상태 머신 구현 등에서 유용합니다.
조건이 복잡할수록 loop와 break 표현식의 장점이 드러납니다. 물론 더 간단한 경우에는 이터레이터 메서드가 좋습니다.
find, position, fold 등의 메서드를 사용하면 더 선언적인 코드를 작성할 수 있습니다. 하지만 복잡한 로직에서는 loop 표현식이 더 적합할 수 있습니다.
주의할 점이 있습니다. loop는 기본적으로 무한히 반복됩니다.
반드시 break 조건을 명확히 하지 않으면 프로그램이 멈추지 않습니다. 또한 모든 break가 같은 타입의 값을 반환해야 합니다.
김개발 씨는 loop 표현식으로 검색 로직을 깔끔하게 정리했습니다. "가변 변수를 쓰지 않아도 되니 코드가 훨씬 안전해졌어요!" loop와 break 표현식을 마스터하면 복잡한 제어 흐름도 깔끔하게 처리할 수 있습니다.
가변 변수를 줄이고, 의도를 명확히 표현하며, 안전한 코드를 작성하세요.
실전 팁
💡 - break 뒤에 값을 붙여서 loop의 반환값으로 사용하세요
- 중첩 루프에서는 레이블('label)로 외부 루프를 탈출할 수 있습니다
- 단순한 검색은 이터레이터 메서드가 더 깔끔할 수 있습니다
6. 함수에서의 표현식 반환
김개발 씨가 간단한 계산 함수를 작성하고 있습니다. 다른 언어에서는 return 키워드를 사용했는데, Rust 코드를 보니 return 없이 그냥 값을 적어놓은 경우가 많더군요.
이게 어떻게 작동하는 걸까요?
Rust 함수에서는 return 키워드 없이 마지막 표현식의 값이 자동으로 반환됩니다. 이것이 Rust의 관용적인 스타일입니다.
return은 함수를 조기에 종료할 때만 사용하고, 정상적인 반환은 표현식으로 처리합니다.
다음 코드를 살펴봅시다.
fn add(a: i32, b: i32) -> i32 {
a + b // 세미콜론 없음 - 표현식으로 반환
}
fn divide(a: i32, b: i32) -> Option<i32> {
if b == 0 {
return None; // 조기 반환에만 return 사용
}
Some(a / b) // 정상 반환은 표현식으로
}
fn classify_age(age: u32) -> &'static str {
match age {
0..=12 => "어린이",
13..=19 => "청소년",
20..=64 => "성인",
_ => "노인",
}
}
fn main() {
// 간단한 표현식 반환
let sum = add(3, 5);
println!("합계: {}", sum);
// 조건부 반환
let result = divide(10, 2);
println!("나눗셈: {:?}", result);
let error = divide(10, 0);
println!("에러 케이스: {:?}", error);
// match 표현식 반환
let category = classify_age(25);
println!("분류: {}", category);
// 블록 표현식으로 복잡한 로직
fn calculate_discount(price: i32, is_member: bool) -> f64 {
let base_rate = if is_member { 0.1 } else { 0.05 };
// 블록 표현식으로 추가 할인 계산
let bonus = {
if price >= 100000 {
0.05
} else if price >= 50000 {
0.03
} else {
0.0
}
};
base_rate + bonus // 최종 할인율 반환
}
let discount = calculate_discount(120000, true);
println!("할인율: {}%", discount * 100.0);
}
계산기 앱을 만들던 김개발 씨는 Rust 코드를 리뷰하다가 이상한 점을 발견했습니다. 함수 끝에 return 키워드가 없는 경우가 많았습니다.
분명히 값을 반환하는 함수인데 말이죠. 직접 return을 붙여서 작성해봤는데, 박러스트 씨가 말렸습니다.
"Rust에서는 그렇게 안 써요. 표현식으로 반환하는 게 관습이에요." Rust의 함수 반환 방식은 독특합니다.
함수의 마지막 표현식이 자동으로 반환값이 됩니다. return 키워드가 필요 없습니다.
쉽게 비유하자면, Rust 함수는 마치 편지의 마지막 문장과 같습니다. 별도로 "이것을 전달합니다"라고 말하지 않아도, 마지막에 적힌 내용이 전달됩니다.
다른 언어에서는 return이 필수였습니다. 하지만 Rust에서는 return이 "조기 종료"를 의미합니다.
함수를 실행하다가 갑자기 중단하고 값을 반환하고 싶을 때만 return을 사용합니다. 이 방식의 장점은 무엇일까요?
첫째, 코드가 간결해집니다. 모든 함수 끝에 return을 붙이는 번거로움이 사라집니다.
둘째, 함수가 하나의 큰 표현식이라는 개념이 명확해집니다. 입력을 받아 출력을 내는 수학적 함수의 개념과 일치합니다.
코드를 살펴봅시다. add 함수는 a + b라는 표현식으로 끝납니다.
세미콜론이 없죠? 이것이 핵심입니다.
세미콜론이 없으면 표현식이고, 함수의 마지막 표현식은 반환값이 됩니다. divide 함수는 조금 더 복잡합니다.
0으로 나누는 경우를 처리하기 위해 return을 사용했습니다. 이것이 return의 올바른 용법입니다.
에러나 예외 상황에서 함수를 조기에 종료할 때 사용합니다. classify_age 함수는 match 표현식으로 끝납니다.
match도 표현식이므로 그대로 반환값이 됩니다. if, match, 블록 모두 표현식이므로 함수의 반환부에 자연스럽게 사용할 수 있습니다.
calculate_discount 함수는 여러 표현식을 조합했습니다. if 표현식, 블록 표현식, 그리고 마지막 덧셈 표현식까지.
모든 것이 자연스럽게 흘러가며 최종적으로 base_rate + bonus가 반환됩니다. 실무에서는 이 스타일이 매우 자연스럽게 느껴집니다.
함수의 핵심 로직이 무엇인지 한눈에 파악할 수 있습니다. 마지막 줄이 반환값이라는 규칙을 알면 코드를 훨씬 빨리 읽을 수 있습니다.
주의할 점이 있습니다. 마지막 표현식에 세미콜론을 붙이면 안 됩니다.
붙이면 구문이 되어 ()가 반환됩니다. 이것은 초보자가 가장 많이 하는 실수 중 하나입니다.
김개발 씨는 처음에는 어색했지만 곧 적응했습니다. "이제 return을 쓰는 게 더 어색하게 느껴져요!" Rust의 표현식 반환 스타일을 마스터하면 더 간결하고 관용적인 코드를 작성할 수 있습니다.
return은 조기 종료에만 사용하고, 정상 반환은 표현식으로 처리하세요.
실전 팁
💡 - 함수 마지막 표현식에 세미콜론을 붙이지 마세요
- return은 조기 종료(에러 처리 등)에만 사용하는 것이 관용적입니다
- 함수 전체를 하나의 큰 표현식으로 생각하면 이해하기 쉽습니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Rust 댕글링 참조 방지하기 완벽 가이드
Rust의 소유권 시스템이 어떻게 댕글링 참조를 컴파일 타임에 방지하는지 알아봅니다. 메모리 안전성을 보장하는 Rust의 핵심 메커니즘을 초보자도 쉽게 이해할 수 있도록 풀어냅니다.
BPE 토크나이저 완전 분석
GPT-4와 같은 대규모 언어 모델이 텍스트를 이해하는 첫 번째 단계인 BPE 토크나이저를 분석합니다. Rust로 구현된 고성능 토크나이저의 내부 구조부터 Python 래퍼, 학습 방법까지 완벽하게 살펴봅니다.
Rust OS 개발 페이지 폴트 핸들링 완벽 가이드
운영체제의 메모리 관리 핵심인 페이지 폴트를 Rust로 구현하는 방법을 다룹니다. CPU 예외 처리부터 페이지 테이블 관리, 실제 핸들러 구현까지 OS 개발의 실전 기술을 배웁니다.
Rust로 만드는 나만의 OS - 실제 하드웨어에서 테스트
QEMU 에뮬레이터를 벗어나 실제 하드웨어에서 직접 만든 OS를 부팅하는 방법을 다룹니다. USB 부팅 디스크 생성부터 BIOS/UEFI 설정, 하드웨어 호환성 문제 해결까지 실전 OS 개발의 모든 과정을 상세히 알아봅니다.
Rust 입문 가이드 45 match 표현식으로 패턴 매칭하기
Rust의 강력한 match 표현식을 활용해 패턴 매칭을 수행하는 방법을 배워봅니다. if-else의 한계를 뛰어넘어 안전하고 표현력 있는 코드를 작성하는 방법을 실무 예제와 함께 알아봅니다.