구문
구문 (Statements)
구문(statement)은 블록(block)의 구성 요소예요. 그리고 블록은 다시 바깥 표현식이나 함수의 구성 요소가 되죠. Rust에서 구문은 두 종류로 나뉘어요. **선언 구문(declaration statement)**과 **표현식 구문(expression statement)**이 그거예요.
출처: Rust Reference
본문
선언 구문
선언 구문은 둘러싼 구문 블록 안에 이름 하나 이상을 도입하는 구문이에요. 도입된 이름은 새 변수나 새 항목(item)을 가리킬 수 있어요. 선언 구문의 두 종류는 **항목 선언(item declaration)**과 let 구문이에요.
항목 선언
항목 선언 구문은 모듈 안의 항목 선언과 문법적 형태가 똑같아요. 구문 블록 안에서 항목을 선언하면 그 항목의 스코프가 그 블록으로 제한돼요. 이 항목에는 정규 경로(canonical path)가 주어지지 않고, 그것이 선언할 수 있는 하위 항목도 마찬가지예요.
단, **구현(implementation)이 정의하는 연관 항목(associated item)**은 예외예요. 항목과, 해당된다면 트레이트가 접근 가능하기만 하면 바깥 스코프에서도 여전히 접근할 수 있어요. 그 외에는 모듈 안에서 항목을 선언하는 것과 의미가 같아요.
여기서 중요한 점은, 항목 선언이 둘러싼 함수의 제네릭 매개변수, 매개변수, 지역 변수를 암묵적으로 캡처하지 않는다는 거예요. 아래 예시에서 inner는 outer_var에 접근할 수 없어요.
#![allow(unused)]
fn main() {
fn outer() {
let outer_var = true;
fn inner() { /* outer_var is not in scope here */ }
inner();
}
}
let 구문
let 구문은 패턴이 주어지는 새 변수 집합을 도입해요. 그 패턴 뒤에는 선택적으로 타입 명시가 오고, 그다음 끝나거나, 초기화 표현식(initializer expression)과 선택적인 else 블록이 이어져요.
타입 명시가 없으면 컴파일러가 타입을 추론해요. 확정적으로 추론하기에 타입 정보가 부족하면 에러를 냅니다.
변수 선언이 도입한 변수들은 선언 지점부터 둘러싼 블록 스코프의 끝까지 보여요. 단, 다른 변수 선언에 가려져(shadowed) 있는 경우는 제외해요.
else 블록이 없으면 패턴은 **반증 불가능(irrefutable)**해야 해요. else 블록이 있으면 패턴은 **반증 가능(refutable)**할 수 있어요.
패턴이 매치하지 않으면(그러려면 반증 가능해야 하죠) else 블록이 실행돼요. 그리고 else 블록은 **반드시 발산(never 타입으로 평가)**해야 해요.
#![allow(unused)]
fn main() {
let (mut v, w) = (vec![1, 2, 3], 42); // The bindings may be mut or const
let Some(t) = v.pop() else { // Refutable patterns require an else block
panic!(); // The else block must diverge
};
let [u, v] = [v[0], v[1]] else { // This pattern is irrefutable, so the compiler
// will lint as the else block is redundant.
panic!();
};
}
표현식 구문
표현식 구문은 표현식을 평가하고 그 결과를 무시하는 구문이에요. 일반적으로 표현식 구문의 목적은 그 표현식을 평가하면서 생기는 **효과(effects)**를 일으키는 거예요.
블록 표현식이나 제어 흐름 표현식만으로 이루어진 표현식은, 구문이 허용된 문맥에서 쓰일 때 끝 세미콜론을 생략할 수 있어요. 그러면 그것이 독립된 구문으로 파싱될지, 다른 표현식의 일부로 파싱될지 모호해질 수 있는데요, 이 경우에는 구문으로 파싱돼요.
구문으로 쓰일 때 ExpressionWithBlock 표현식의 타입은 유닛 타입이어야 해요.
#![allow(unused)]
fn main() {
let mut v = vec![1, 2, 3];
v.pop(); // Ignore the element returned from pop
if v.is_empty() {
v.push(5);
} else {
v.remove(0);
} // Semicolon can be omitted.
[1]; // Separate expression statement, not an indexing expression.
}
끝 세미콜론을 생략했을 때는 그 결과가 타입 ()이어야 해요.
#![allow(unused)]
fn main() {
// bad: the block's type is i32, not ()
// Error: expected `()` because of default return type
// if true {
// 1
// }
// good: the block's type is i32
if true {
1
} else {
2
};
}
구문의 속성
구문은 **바깥 속성(outer attribute)**을 받을 수 있어요. 구문에 의미가 있는 속성은 cfg와 린트 검사 속성들이에요.
더 알아보기
- 패턴이 반증 가능/반증 불가능이 언제인지,
letelse가 어떻게 동작하는지를 살펴보면 let 구문을 더 잘 이해할 수 있어요. - 블록 표현식과 유닛 타입의 관계를 보면 세미콜론 생략 규칙이 왜 그런지 명확해져요.