コンテンツにスキップ

Rustのエラー E0434: can't capture dynamic environment in a fn item(内側のfnから外の変数は使えない)

E0434 は、関数の中に書いた関数が、外側の関数のローカル変数を使おうとした、という意味のエラーです。動きそうに見えますよね。変数は、ほんの数行上にあるのですから。でも Rust では、入れ子にした fn は独立したアイテムなので、周りの変数は見えません。コンパイラの言う「dynamic environment」(動的な環境)は、「外側の関数が動いている間だけ存在するローカル変数」という意味にすぎません。これはいちばん直しやすいエラーの1つです。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) // ❌ inner `fn` can't see `tax_rate`
| ^^^^^^^^
|
= help: use the `|| { ... }` closure form instead

「can’t capture dynamic environment in a fn item」は「fn アイテムでは動的な環境をキャプチャできない」、help は「代わりに || { ... } というクロージャの形を使ってください」という意味です。

このエラーが出るのは、次のプログラムです。

fn main() {
let tax_rate = 0.2;
fn with_tax(price: f64) -> f64 {
price * (1.0 + tax_rate) // ❌ inner `fn` can't see `tax_rate`
}
println!("{:.2}", with_tax(10.0));
}

コメントは「内側の fn からは tax_rate が見えない」という意味です(エラーになる例なので、コードはそのままにしています)。

^^^ は5行目の tax_rate を指しています。これは main のローカル変数で、2行目で let を使って作ったものです。内側の関数 with_tax がそれを読もうとしていますが、それは許されていません。

help: の行が直し方を教えてくれています。入れ子の fn の代わりに、クロージャ(closure)|| { ... } を書きましょう。クロージャは周りの変数を使えます。fn は、どこに書いても使えません。

unused variable: `tax_rate` (使われていない変数 tax_rate)という警告も一緒に出ることがよくあります。Rust から見ると、その変数を正しく使えるものが何もないからです。この警告は、エラーを直せば一緒に消えます。

机で書いて封をした手紙のようなものです。書いているときに机の上にメモがあったとしても、手紙を開けた人に見えるのは、封筒に入れたものだけです。

入れ子の fn は、ファイルのトップレベルにある関数とまったく同じようにコンパイルされます。main の中に書いても、ほかのコードから名前が隠れるだけです。main の変数とつながるわけではありません。それにはちゃんとした理由があります。ふつうの関数には、変数をしまっておく場所がないのです。with_tax は、main から届くところならどこからでも呼べますし、関数ポインタとして保存することも、main の変数がなくなったあとで呼ぶこともできます。ふつうの fn はただのコードで、データは付いていません。

クロージャは、コードとキャプチャした変数をセットにしたものです。Rust はその値(またはその参照)をクロージャの中にしまい、借用チェッカーがそれらが十分長く生きることを確かめます。だから rustc はクロージャを勧めるのです。詳しくは周りのスコープから変数をキャプチャするを見てください。

同じルールはジェネリクスの型引数にも当てはまります。内側の fn は、外側の関数の T も使えません。そちらは E0401 です。

たいていはこれを選びます。小さなヘルパーで、1つの関数の中だけで使うときです。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 にする必要があります。詳しくはクロージャを見てください。

ヘルパーを本物の関数のままにしておきたいときは、これを選びます。長い関数だったり、テストしたかったり、いくつもの関数から使ったりする場合です。引数を1つ増やして、値を渡しましょう。

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 のヘルパー関数は、たいていトップレベルに置きます。入れ子のままでも動きます。大事なのは、値が引数として入ってくることです。おまけに、1つの税率だけでなく、どんな税率でも使えるようになりました。詳しくは引数:関数にデータを渡すを見てください。

値が決まっていて、プログラムが動く前からわかっているとき(コードに書かれた税率など)は、これを選びます。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); } } も同じ E0434 になり、self が指されます。直し方: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; と書きます。

  • E0401:can’t use generic parameters from outer item。型についての同じルールで、内側の fn は外側の T や Self を使えません。
  • E0425:cannot find value in this scope。その名前がまったく存在しない。たとえば、つづりの間違いや、別の関数の変数を使ったとき。
  • E0373:closure may outlive the current function。クロージャに切り替えたあと、借用した変数よりクロージャが長く生きると出ることがあります(直し方:move)。
  • E0424:self is not available here。メソッドではない関数の中で self を使った。
  • E0435:let の変数を使おうとした const。直し方3で紹介しました。
  • コンパイルエラーの一覧:エラー解説ページの全リストです。

理解度チェック

1. main の中に let limit = 10; と、入れ子の fn check(n: i32) -> bool { n < limit } があります。どうなりますか?

2. ローカル変数 limit を使えるのはどれですか?

3. 値は実行時にユーザーの入力から来ます。うまくいかない直し方はどれですか?

スコア: 0 / 3