콘텐츠로 이동

Rust 에러 E0434: can't capture dynamic environment in a fn item (안쪽 fn의 변수 캡처)

E0434는 다른 함수 안에 쓴 함수가 바깥 함수의 지역 변수를 쓰려고 했다는 뜻이에요. 될 것처럼 보이죠. 변수가 바로 몇 줄 위에 있으니까요. 하지만 Rust에서 중첩된 fn은 독립된 아이템(item)이라서, 주변의 변수를 볼 수 없어요. 컴파일러가 말하는 “dynamic environment”(동적 환경)는 그냥 “바깥 함수가 실행되는 동안 존재하는 지역 변수들”이라는 뜻이에요. 고치기 가장 쉬운 에러 중 하나예요. rustc가 help: 줄에서 답을 알려 주거든요. 이 페이지는 Rust 컴파일러 에러 해설의 일부예요.

error[E0434]: can't capture dynamic environment in a fn item
--> src/main.rs:5:24
|
5 | price * (1.0 + tax_rate) // ❌ 안쪽 `fn`은 `tax_rate`를 볼 수 없어요
| ^^^^^^^^
|
= help: use the `|| { ... }` closure form instead

help:는 “대신 || { ... } 클로저 형태를 쓰라”는 뜻이에요.

이 에러를 내는 프로그램은 다음과 같아요.

fn main() {
let tax_rate = 0.2;
fn with_tax(price: f64) -> f64 {
price * (1.0 + tax_rate) // ❌ 안쪽 `fn`은 `tax_rate`를 볼 수 없어요
}
println!("{:.2}", with_tax(10.0));
}

^^^는 5번째 줄의 tax_rate를 가리켜요. 이건 2번째 줄에서 let으로 만든 main의 지역 변수예요. 안쪽 함수 with_tax가 그걸 읽으려고 하는데, 그건 허용되지 않아요.

help: 줄이 해결책을 알려 줘요. 중첩된 fn 대신 클로저(closure) || { ... }를 쓰라는 거예요. 클로저는 주변의 변수를 쓸 수 있어요. fn은 어디에 쓰든 그럴 수 없어요.

unused variable: `tax_rate` (사용하지 않는 변수 tax_rate)라는 경고도 함께 나오는 경우가 많아요. Rust 입장에서는 그 변수를 정당하게 쓸 수 있는 곳이 없으니까요. 이 경고는 에러를 고치면 같이 사라져요.

책상에서 쓴 봉인된 편지와 비슷해요. 편지를 쓰는 동안 책상 위에 메모가 있었다는 건 상관없어요. 편지를 여는 사람은 봉투 안에 넣은 것만 볼 수 있으니까요.

중첩된 fn은 파일 맨 위에 쓴 함수와 똑같이 컴파일돼요. main 안에 쓰면 나머지 코드에서 그 이름이 안 보이게 될 뿐이에요. main의 변수와 연결되지는 않아요. 여기에는 이유가 있어요. 평범한 함수에는 그 변수들을 담아 둘 곳이 없거든요. with_tax는 main이 닿는 곳이라면 어디서든 호출할 수 있고, 함수 포인터로 저장할 수도 있고, main의 변수가 사라진 뒤에 호출할 수도 있어요. 평범한 fn은 데이터가 붙어 있지 않은, 코드일 뿐이에요.

클로저는 코드 더하기 캡처한 변수예요. Rust는 그 값(또는 그 값의 참조자)을 클로저 안에 저장하고, 대여 검사기(borrow checker)가 그 값이 충분히 오래 살아 있는지 확인해요. 그래서 rustc가 클로저를 권하는 거예요. 주변 스코프의 변수 캡처하기를 참고하세요.

제네릭 매개변수에도 같은 규칙이 적용돼요. 안쪽 fn은 바깥 함수의 T도 쓸 수 없어요. 그건 E0401이에요.

도우미 코드가 작고 한 함수 안에서만 쓰일 때, 대부분은 이 방법을 고르세요. let 이름 = |매개변수| 본문;이라고 쓰면, 클로저는 스코프 안의 어떤 변수든 쓸 수 있어요.

fn main() {
let tax_rate = 0.2;
let with_tax = |price: f64| price * (1.0 + tax_rate); // ✅ 클로저는 `tax_rate`를 볼 수 있어요
println!("{:.2}", with_tax(10.0));
println!("{:.2}", with_tax(24.5));
let prices = [3.0, 7.5, 12.0];
let total: f64 = prices.iter().map(|&p| with_tax(p)).sum();
println!("합계: {:.2}", total);
}

실행 결과:

12.00
29.40
합계: 27.00
▶ 직접 실행해 보기

변수를 읽기만 하는 클로저는 그 변수를 빌려요. 그래서 그 뒤에도 tax_rate를 계속 쓸 수 있어요. 클로저 안에서 변수를 바꾼다면 클로저에 let mut이 필요해요. 클로저를 참고하세요.

도우미가 진짜 함수로 남아야 할 때 이 방법을 고르세요. 코드가 길거나, 테스트하고 싶거나, 여러 함수가 함께 쓰는 경우예요. 매개변수를 하나 더 주고 값을 넘기세요.

fn with_tax(price: f64, tax_rate: f64) -> f64 {
price * (1.0 + tax_rate) // ✅ 이제 `tax_rate`는 매개변수예요
}
fn receipt(prices: &[f64], tax_rate: f64) -> f64 {
prices.iter().map(|&p| with_tax(p, tax_rate)).sum()
}
fn main() {
let tax_rate = 0.2;
println!("{:.2}", with_tax(10.0, tax_rate));
println!("합계: {:.2}", receipt(&[3.0, 7.5, 12.0], tax_rate));
let reduced_rate = 0.05;
println!("{:.2}", with_tax(10.0, reduced_rate));
}

실행 결과:

12.00
합계: 27.00
10.50
▶ 직접 실행해 보기

여기서는 함수를 맨 바깥으로 옮겼어요. Rust의 도우미 함수는 대부분 그 자리에 있어요. 중첩된 채로 둬도 동작해요. 중요한 건 값이 인자로 들어온다는 점이에요. 덤으로, 이제 한 가지 세율이 아니라 어떤 세율이든 쓸 수 있어요. 매개변수를 참고하세요.

세율처럼 코드에 적혀 있어서 프로그램이 실행되기 전에 값이 정해져 있다면 이 방법을 고르세요. let 변수와 달리 const는 아이템이라서, 중첩됐든 아니든 어떤 함수든 쓸 수 있어요.

const TAX_RATE: f64 = 0.2; // ✅ 상수는 모든 함수에서 보여요
fn main() {
fn with_tax(price: f64) -> f64 {
price * (1.0 + TAX_RATE)
}
println!("{:.2}", with_tax(10.0));
println!("{:.2}", with_tax(24.5));
}

실행 결과:

12.00
29.40
▶ 직접 실행해 보기

이 방법은 컴파일할 때 계산할 수 있는 값에만 통해요. 세율이 파일, 사용자 입력, 다른 변수에서 온다면 const로는 안 돼요. const RATE: f64 = tax_rate;는 다른 에러인 E0435(attempt to use a non-constant value in a constant, 상수에 상수가 아닌 값을 쓰려고 했다)를 내요. 그럴 땐 방법 1이나 방법 2를 쓰세요. 상수를 참고하세요.

JavaScript나 Python을 쓰다 온 경우. 그 언어들에서는 중첩된 함수가 바깥 변수를 볼 수 있어서, fn 안의 fn이 자연스럽게 느껴져요. Rust에서 그 역할을 하는 건 클로저예요. 해결: fn helper(x) ... 대신 let helper = |x| ...;라고 쓰세요.

메서드 안의 중첩된 fn에서 self를 쓰는 경우. fn report(&self) { fn show() { println!("{}", self.n); } }도 self를 가리키며 같은 E0434를 내요. 해결: show를 메서드로 만들고(impl 안에 fn show(&self)) self.show()로 호출하거나, 클로저를 쓰세요.

let이 아니라 함수 매개변수인 경우. fn total(prices: &[f64], discount: f64) 안에 중첩된 fn apply(price: f64) -> f64 { price - discount }도 E0434를 내요. 매개변수도 지역 변수거든요. 해결: discount를 apply에 넘기거나, let apply = |price: f64| price - discount;라고 쓰세요.

이해도 점검

1. main 안에 let limit = 10;과 중첩된 fn check(n: i32) -> bool { n < limit }이 있어요. 어떻게 될까요?

2. 지역 변수 limit을 쓸 수 있는 건 어느 것일까요?

3. 값이 실행 중에 사용자 입력으로 들어와요. 어떤 해결책이 통하지 않을까요?

점수: 0 / 3