Panic: Stack Overflow
O stack overflow ocorre quando o programa esgota o espaço disponível na stack (pilha de execução). As causas mais comuns são recursão infinita (ou muito profunda) e tipos muito grandes alocados na stack.
A Mensagem de Erro
thread 'main' panicked at 'thread has overflowed its stack', src/main.rs:3:5
thread 'main' has overflowed its stack
fatal runtime error: stack overflow
Diferente de outros panics, o stack overflow pode não exibir um backtrace completo, pois não há espaço na stack nem para executar o handler de panic.
O Que Significa
Todo programa tem uma stack — uma região de memória fixa (geralmente 8 MB no Linux, 1 MB no Windows) usada para:
- Variáveis locais de funções
- Parâmetros de funções
- Endereços de retorno (call stack)
Cada chamada de função adiciona um stack frame à pilha. Se a recursão for muito profunda ou se variáveis locais forem muito grandes, a stack transborda.
O Rust não tem tail call optimization (TCO) garantida pelo compilador, então mesmo recursão em posição final pode causar stack overflow com profundidade suficiente.
Código com Erro
Recursão Infinita
fn contar(n: u64) -> u64 {
// Esqueceu o caso base — recursão infinita!
contar(n + 1)
}
fn main() {
println!("{}", contar(0)); // STACK OVERFLOW
}
Recursão Muito Profunda
fn fatorial(n: u64) -> u64 {
if n <= 1 {
1
} else {
n * fatorial(n - 1) // Cada chamada usa mais stack
}
}
fn main() {
// Para n grande, estoura a stack
println!("{}", fatorial(1_000_000)); // STACK OVERFLOW
}
Tipo Grande na Stack
fn main() {
// Array de 10 milhões de f64 = ~80 MB na stack
let dados = [0.0_f64; 10_000_000]; // STACK OVERFLOW
println!("Tamanho: {}", dados.len());
}
Recursão Mútua
fn par(n: u64) -> bool {
if n == 0 { true } else { impar(n - 1) }
}
fn impar(n: u64) -> bool {
if n == 0 { false } else { par(n - 1) }
}
fn main() {
println!("{}", par(1_000_000)); // STACK OVERFLOW
}
Como Resolver
Solução 1: Converter Recursão em Iteração
A solução mais eficaz — loops não consomem stack adicional:
fn fatorial(n: u64) -> u64 {
let mut resultado: u64 = 1;
for i in 2..=n {
resultado = resultado.saturating_mul(i);
}
resultado
}
fn main() {
println!("{}", fatorial(20)); // Funciona perfeitamente
}
Fibonacci iterativo:
fn fibonacci(n: u64) -> u64 {
if n <= 1 {
return n;
}
let mut a: u64 = 0;
let mut b: u64 = 1;
for _ in 2..=n {
let temp = a + b;
a = b;
b = temp;
}
b
}
fn main() {
println!("Fib(50) = {}", fibonacci(50));
}
Solução 2: Usar Box para Alocar no Heap
Para dados grandes, use Box<T> que aloca no heap (sem limite de tamanho fixo):
fn main() {
// Alocado no HEAP via Box — sem stack overflow
let dados = vec![0.0_f64; 10_000_000];
println!("Tamanho: {}", dados.len());
// Ou com Box para arrays
let array: Box<[f64]> = vec![0.0; 10_000_000].into_boxed_slice();
println!("Tamanho: {}", array.len());
}
Para structs grandes:
struct DadosGrandes {
buffer: Box<[u8; 1_000_000]>, // 1 MB no heap
nome: String,
}
impl DadosGrandes {
fn novo(nome: String) -> Self {
DadosGrandes {
buffer: Box::new([0u8; 1_000_000]),
nome,
}
}
}
Solução 3: Aumentar o Tamanho da Stack
Para threads específicas, defina um tamanho de stack maior:
use std::thread;
fn recursao_profunda(n: u64) -> u64 {
if n <= 1 { 1 } else { n + recursao_profunda(n - 1) }
}
fn main() {
// Thread com 64 MB de stack
let builder = thread::Builder::new().stack_size(64 * 1024 * 1024);
let handler = builder.spawn(|| {
println!("{}", recursao_profunda(500_000));
}).unwrap();
handler.join().unwrap();
}
Nota: Aumentar a stack é um paliativo, não uma solução. Prefira converter para iteração.
Solução 4: Técnica de Trampolining
Para manter o estilo recursivo sem estourar a stack:
enum Trampolim<T> {
Feito(T),
Continuar(Box<dyn FnOnce() -> Trampolim<T>>),
}
fn executar<T>(mut t: Trampolim<T>) -> T {
loop {
match t {
Trampolim::Feito(valor) => return valor,
Trampolim::Continuar(f) => t = f(),
}
}
}
fn fatorial_tramp(n: u64, acumulador: u64) -> Trampolim<u64> {
if n <= 1 {
Trampolim::Feito(acumulador)
} else {
Trampolim::Continuar(Box::new(move || {
fatorial_tramp(n - 1, n * acumulador)
}))
}
}
fn main() {
let resultado = executar(fatorial_tramp(100_000, 1));
println!("Concluído");
}
Solução 5: Usar Stack Explícita (Simulação de Recursão)
Substitua a call stack por uma estrutura de dados explícita:
fn percorrer_arvore(raiz: Option<Box<No>>) {
let mut pilha = Vec::new();
if let Some(no) = raiz {
pilha.push(no);
}
while let Some(no) = pilha.pop() {
println!("{}", no.valor);
if let Some(direita) = no.direita {
pilha.push(direita);
}
if let Some(esquerda) = no.esquerda {
pilha.push(esquerda);
}
}
}
struct No {
valor: i32,
esquerda: Option<Box<No>>,
direita: Option<Box<No>>,
}
Quando Usar Cada Solução
| Problema | Solução recomendada |
|---|---|
| Recursão simples (fatorial, soma) | Converter para loop |
| Traversal de árvore/grafo | Stack explícita com Vec |
| Array/struct grande | Vec, Box, ou alocação no heap |
| Recursão inevitável e profunda | Aumentar stack da thread |
| Manter estilo funcional | Trampolining |