Pular para o conteúdo

Erro E0434 do Rust: can't capture dynamic environment in a fn item (fn interna)

O E0434 quer dizer que uma função escrita dentro de outra função tentou usar uma das variáveis locais da função de fora. Parece que deveria funcionar: a variável está logo ali, algumas linhas acima. Mas em Rust um fn aninhado é um item separado, e ele não enxerga as variáveis ao redor. A expressão do compilador, “dynamic environment” (ambiente dinâmico), quer dizer apenas “as variáveis locais que existem enquanto a função de fora está rodando”. Este é um dos erros mais fáceis de corrigir: o rustc te dá a resposta na linha help:. Esta página faz parte do guia de erros do compilador.

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

Em português: “não é possível capturar o ambiente dinâmico num item fn”. A ajuda sugere usar a forma de closure, || { ... }.

Este é o programa que gera o erro:

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));
}

(O comentário quer dizer “o fn interno não enxerga tax_rate”. Ele fica em inglês porque aparece na saída do compilador.)

O ^^^ aponta para tax_rate na linha 5. Essa é uma variável local de main, criada com let na linha 2. A função interna with_tax tenta ler essa variável, e isso não é permitido.

A linha help: dá a correção: escreva uma closure (função anônima), || { ... }, no lugar de um fn aninhado. Uma closure pode usar as variáveis ao redor. Um fn não pode, não importa onde você o escreva.

Muitas vezes você também recebe um aviso, unused variable: `tax_rate` (variável não usada), porque, do ponto de vista do Rust, nada poderia usá-la legalmente. Esse aviso some junto com o erro.

É como uma carta lacrada que você escreveu na sua mesa. Não importa que as suas anotações estavam na mesa enquanto você escrevia; quem abre a carta só vê o que você colocou dentro do envelope.

Um fn aninhado é compilado exatamente como um que está no topo do arquivo. Escrevê-lo dentro de main só esconde o nome dele do resto do código. Isso não o liga às variáveis de main, e há um bom motivo: uma função comum não tem onde guardá-las. Você pode chamar with_tax de qualquer lugar onde main a alcança, guardá-la como ponteiro de função ou chamá-la depois que as variáveis de main já sumiram. Um fn comum é só código, sem dados junto.

Uma closure é código mais as variáveis que ela capturou. O Rust guarda esses valores (ou referências a eles) dentro da closure, e o borrow checker garante que eles vivam tempo suficiente. É por isso que o rustc te manda para as closures. Veja Capturando variáveis do escopo ao redor.

A mesma regra vale para parâmetros genéricos: um fn interno também não pode usar o T da função de fora. Esse é o E0401.

Escolha esta na maioria das vezes, quando a função auxiliar é pequena e vive dentro de uma função só. Escreva let nome = |parâmetros| corpo; e a closure pode usar qualquer variável do escopo:

fn main() {
let tax_rate = 0.2;
let with_tax = |price: f64| price * (1.0 + tax_rate); // ✅ uma closure enxerga `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!("total: {:.2}", total);
}

Saída:

12.00
29.40
total: 27.00
▶ Experimente você mesmo

Uma closure que só lê uma variável pega ela emprestada, então você ainda pode usar tax_rate depois. Se você mudar a variável dentro da closure, a closure precisa de let mut. Veja Closures.

Escolha esta quando a função auxiliar deve continuar sendo uma função de verdade: ela é mais longa, você quer testá-la ou várias funções a usam. Dê a ela mais um parâmetro e passe o valor:

fn with_tax(price: f64, tax_rate: f64) -> f64 {
price * (1.0 + tax_rate) // ✅ agora `tax_rate` é um parâmetro
}
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!("total: {:.2}", receipt(&[3.0, 7.5, 12.0], tax_rate));
let reduced_rate = 0.05;
println!("{:.2}", with_tax(10.0, reduced_rate));
}

Saída:

12.00
total: 27.00
10.50
▶ Experimente você mesmo

Aqui a função foi para o nível de cima do arquivo, que é onde vive a maioria das funções auxiliares em Rust. Também funciona se você a mantiver aninhada; o que importa é que o valor entra como argumento. De bônus, agora ela funciona com qualquer alíquota, não só com uma. Veja Parâmetros.

Escolha esta quando o valor é fixo e conhecido antes de o programa rodar, como uma alíquota de imposto escrita no código. Diferente de uma variável let, um const é um item, e qualquer função pode usá-lo, aninhada ou não:

const TAX_RATE: f64 = 0.2; // ✅ uma constante, visível para todas as funções
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));
}

Saída:

12.00
29.40
▶ Experimente você mesmo

Isso só funciona para valores que podem ser calculados em tempo de compilação. Se a alíquota vem de um arquivo, da entrada do usuário ou de outra variável, um const não serve: const RATE: f64 = tax_rate; dá outro erro, o E0435 (attempt to use a non-constant value in a constant, tentativa de usar um valor não constante numa constante). Nesse caso, use a Correção 1 ou a 2. Veja Constantes.

Quem vem do JavaScript ou do Python. Nessas linguagens, uma função aninhada enxerga as variáveis de fora, então fn dentro de fn parece natural. Em Rust, quem faz isso é a closure. Correção: let helper = |x| ...; no lugar de fn helper(x) ....

Usar self num fn aninhado dentro de um método. fn report(&self) { fn show() { println!("{}", self.n); } } dá o mesmo E0434, apontando para self. Correção: transforme show num método (fn show(&self) no impl) e chame self.show(), ou use uma closure.

Um parâmetro da função, não um let. Em fn total(prices: &[f64], discount: f64), um fn apply(price: f64) -> f64 { price - discount } aninhado também dá E0434: os parâmetros também são variáveis locais. Correção: passe discount para apply, ou escreva let apply = |price: f64| price - discount;.

Teste o que aprendeu

1. Dentro de main, você tem let limit = 10; e um fn check(n: i32) -> bool { n < limit } aninhado. O que acontece?

2. Qual versão pode usar a variável local limit?

3. O valor vem da entrada do usuário, enquanto o programa roda. Qual correção NÃO vai funcionar?

Pontuação: 0 / 3