4.10. 중첩 함수
함수의 본체 안에서 또 다른 함수를 정의할 수 있습니다.
4.10.1. 형태
fn outer(x: i32) -> i32 {
fn inner(y: i32) -> i32 {
y + 1
}
inner(x) + inner(x * 2)
}
fn 키워드를 외부 함수의 본체 안에 두기만 하면 되며, 정의와 사용에 관한 규칙은 함수 선언 에서 다루는 모듈 수준 함수와 동일합니다.
4.10.2. 가시 범위
중첩 함수는 자신이 정의된 블록 스코프 안에서만 보입니다.
fn outer() {
fn helper() { ... }
helper(); // OK
}
helper(); // ERROR - outer 밖에서는 보이지 않음
해당 블록을 벗어나는 순간 중첩 함수의 이름은 더 이상 보이지 않게 됩니다.
4.10.3. 외부 변수 캡처 (없음)
중첩 함수는 외부 함수의 변수를 캡처하지 않습니다.
fn outer(n: i32) {
fn inner() {
let x = n; // ERROR - n 이 보이지 않음
}
inner();
}
이러한 결정은 중첩 함수가 깨끗한 함수 단위라는 점을 분명히 드러내기 위한 것입니다. 외부 변수에 의존하는 상태 있는 함수가 필요할 때에는 클로저 를 사용하면 됩니다.
fn outer(n: i32) {
let inner = |x| x + n; // OK - 클로저는 캡처 가능
inner(5);
}
이로써 다음과 같은 분리가 이루어집니다.
- 중첩 함수는 외부 변수를 보지 못하며, 가시 범위가 제한된다는 점을 제외하면 모듈 수준 함수와 같습니다.
- 클로저는 캡처가 가능합니다 (캡처 의미론 을 참조합니다).
중첩 함수는 자기 블록과 둘러싼 블록들의 항목과 use 선언을 const 를 포함해 봅니다 (3.9.1). 닿지 못하는 것은 그 블록들의 지역 변수, 매개변수, 제네릭 매개변수뿐입니다.
4.10.4. 호출 시 동작
중첩 함수의 호출은 모듈 수준 함수의 호출과 동일한 규칙을 따릅니다. 함수 주소를 통해 직접 호출하므로 캡처도 오버헤드도 발생하지 않습니다.
4.10.5. 정의 순서
같은 블록 안에서는 중첩 함수의 정의 전후 어디에서든 호출할 수 있습니다. 다음 두 형태 모두 허용됩니다.
fn outer() {
helper(); // 정의 전 호출
fn helper() { ... }
}
fn outer() {
fn helper() { ... }
helper(); // 정의 후 호출
}
호이스팅 (Hoisting) 덕분에 블록 전체에서 보이게 되므로, 어느 위치에서든 호출할 수 있습니다.
4.10.6. 중첩 함수와 클로저 사이의 선택
| 특성 | 중첩 함수 | 클로저 |
|---|---|---|
| 정의 형태 | fn name(...) { ... } | | ... | body |
| 외부 변수 캡처 | 불가 | 가능 |
| 이름 | 있음 | 없음 |
| 다른 함수에 인자로 전달 | 가능 (이름으로) | 가능 |
| 상태 없는 도우미 | 적합 | 가능하지만 과도 |
| 상태 있는 콜백 | 부적합 | 적합 |
선택 기준은 다음과 같습니다. 외부 변수가 필요 없고 이름이 있어야 가독성에 도움이 된다면 중첩 함수를 선택하고, 외부 변수가 필요하거나 익명으로 충분한 경우라면 클로저를 사용하면 됩니다.
4.10.7. 중첩 함수의 가시성
중첩 함수는 외부 함수의 본체 안에서만 보이므로, pub 같은 가시성 키워드는 의미를 갖지 못합니다.
fn outer() {
pub fn helper() { ... } // ERROR - 중첩 함수에 pub 무의미
}
가시성은 모듈 수준의 항목에만 적용됩니다. 블록에 선언한 모든 항목도 마찬가지입니다 (3.9.1).
4.10.8. 같은 이름의 중첩 함수와 외부 변수
fn outer() {
let helper = 42;
fn helper() { ... } // ERROR - 같은 이름의 변수와 함수
}
같은 블록 안에서 같은 이름을 가진 변수와 함수는 서로 충돌하므로 컴파일 에러가 발생합니다.
4.10.9. 다단 중첩
함수는 임의의 깊이로 중첩할 수 있습니다.
fn outer() {
fn middle() {
fn inner() { ... }
inner();
}
middle();
}
중첩 함수는 자기 블록 전체에서 보입니다 (hoist). 자기 블록과 바깥 모든 블록의 중첩 함수, 그 밖의 항목, use 선언은 보지만, 그 어느 블록의 지역 변수도 보지 않습니다. 블록의 use 는 같은 이름의 바깥 중첩 함수를 가립니다.
4.10.10. 중첩 함수의 유용한 곳
fn parse(input: str) -> AST {
fn parse_expr(s: str) -> Expr { ... }
fn parse_stmt(s: str) -> Stmt { ... }
let result = AST::new();
while !input.is_empty() {
result.add(parse_stmt(input));
}
result
}
큰 함수의 내부 도우미로 활용할 때 자연스럽습니다. 다른 모듈에 노출할 필요가 없고 외부 함수의 컨텍스트와 묶여 있는 보조 로직이라면 중첩 함수가 잘 어울립니다.