Panic: Stack Overflow no Rust

Como resolver stack overflow no Rust: causas como recursão infinita, tipos grandes na stack e soluções com iteração, Box, aumento de stack e trampolining.

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

ProblemaSolução recomendada
Recursão simples (fatorial, soma)Converter para loop
Traversal de árvore/grafoStack explícita com Vec
Array/struct grandeVec, Box, ou alocação no heap
Recursão inevitável e profundaAumentar stack da thread
Manter estilo funcionalTrampolining

Veja Também