<h2>Stack vs Heap em Rust: Como a Memória é Gerenciada</h2>
<h3>O que são Stack e Heap?</h3>
<p>Stack e Heap são duas regiões de memória fundamentalmente diferentes. O Stack é uma estrutura de dados Last In, First Out (LIFO) que armazena dados de tamanho conhecido em tempo de compilação. Quando uma função é chamada, seus dados são empilhados; ao retornar, são removidos automaticamente. O Heap, por outro lado, é uma região de memória mais flexível para dados cujo tamanho pode variar em tempo de execução. Acessar dados no Heap é mais lento porque requer um ponteiro (endereço de memória), enquanto o Stack oferece acesso direto e muito mais rápido.</p>
<p>Em Rust, essa distinção é crucial porque o sistema de ownership trabalha sobre essas duas regiões de formas distintas. Variáveis simples como inteiros, booleanos e referências vivem no Stack. Estruturas de dados que crescem dinamicamente, como <code>String</code>, <code>Vec</code>, <code>HashMap</code>, vivem no Heap com um ponteiro no Stack. Compreender essa divisão é essencial para dominar Rust e evitar erros de borrow checker.</p>
<pre><code class="language-rust">fn exemplo_stack_heap() {
// Stack: tamanho fixo, conhecido em tempo de compilação
let x: i32 = 5; // 4 bytes no Stack
let y: i32 = x; // Cópia eficiente, outro valor no Stack
// Heap: tamanho dinâmico, ponteiro armazenado no Stack
let s1 = String::from("Rust"); // Dados "Rust" no Heap, ponteiro em s1 no Stack
let s2 = s1; // Ownership transferido, s1 não é mais válido
println!("x = {}, y = {}, s2 = {}", x, y, s2);
// println!("{}", s1); // ERRO: s1 não possui ownership mais
}</code></pre>
<h3>Ownership e Gerenciamento Automático de Memória</h3>
<p>Rust não usa garbage collection como Python ou Java. Em vez disso, implementa o sistema de <strong>ownership</strong> que garante segurança de memória em tempo de compilação. A regra fundamental é: cada valor tem um único proprietário, e quando o proprietário sai de escopo, a memória é liberada automaticamente. Para dados no Stack, isso é trivial. Para dados no Heap, Rust chama automaticamente o método <code>drop()</code> quando o valor sai de escopo.</p>
<p>Quando você move um valor (transfere ownership), o anterior se torna inválido. Isso evita a libertação dupla de memória (double free), um bug clássico em C/C++. Para compartilhar acesso sem transferir ownership, Rust fornece referências imutáveis (<code>&T</code>) e mutáveis (<code>&mut T</code>). As referências vivem no Stack e apontam para dados, mas o borrow checker garante que nunca haja data races ou acesso inválido.</p>
<pre><code class="language-rust">fn propriedade_em_acao() {
let s1 = String::from("Hello");
let s2 = s1; // Ownership movido para s2
// println!("{}", s1); // ERRO: s1 já não é proprietário
println!("{}", s2); // OK: s2 é o proprietário agora
}
fn usando_referencias() {
let s1 = String::from("Rust");
let len = calcular_tamanho(&s1); // Referência imutável
println!("Tamanho de '{}': {}", s1, len);
}
fn calcular_tamanho(s: &String) -> usize {
s.len()
// 's' sai de escopo aqui, mas não libera memória
// porque não é proprietário, apenas referencia
}
fn modificar_string(s: &mut String) {
s.push_str(" é incrível");
}
fn usando_mutabilidade() {
let mut s = String::from("Rust");
modificar_string(&mut s); // Referência mutável
println!("{}", s); // "Rust é incrível"
}</code></pre>
<h3>Stack vs Heap: Implicações de Performance</h3>
<p>Dados no Stack são extremamente rápidos de alocar e desalocar porque é apenas mover um ponteiro (operação O(1)). O compilador conhece o tamanho exato em tempo de compilação, então a alocação é determinística. Heap, por sua vez, requer buscar bloco de memória disponível (mais lento), especialmente em programas com muita alocação dinâmica. Por isso, Rust incentiva colocar dados no Stack sempre que possível.</p>
<p>Tipos que implementam <code>Copy</code> (como inteiros, floats, booleanos) residem integralmente no Stack e são duplicados ao serem atribuídos, não movidos. Tipos mais complexos como <code>String</code> e <code>Vec</code> residem no Heap e usam move semantics por padrão, evitando cópias custosas. Você pode implementar <code>Clone</code> para duplicar explicitamente valores no Heap, mas isso tem custo de performance.</p>
<pre><code class="language-rust">fn stack_vs_heap_performance() {
// STACK: cópia rápida (implementa Copy)
let a = 42i32;
let b = a; // Cópia eficiente: apenas 4 bytes copiados
println!("a: {}, b: {}", a, b); // Ambos válidos
// HEAP: move (String não implementa Copy)
let s1 = String::from("Stack?");
let s2 = s1; // Move eficiente: só o ponteiro foi transferido
// println!("{}", s1); // ERRO
// Clone explícito (caro)
let s3 = String::from("Clone");
let s4 = s3.clone(); // Cópia custosa: String inteira duplicada no Heap
println!("s3: {}, s4: {}", s3, s4); // Ambos válidos agora
}
fn exemplo_vetores() {
let mut v = Vec::new(); // Alocação no Heap
v.push(1);
v.push(2);
v.push(3);
// Internamente: [1, 2, 3] no Heap, com ponteiro + capacity + len no Stack
let v2 = v; // Move eficiente
println!("{:?}", v2);
// println!("{:?}", v); // ERRO: v já não possui os dados
}</code></pre>
<h3>Ciclo de Vida (Lifetimes) e Referências</h3>
<p>Rust formaliza o conceito de "tempo de vida" de uma referência através de <strong>lifetimes</strong>. Toda referência tem um lifetime (anotado com <code>'a</code>, <code>'b</code>, etc.) que indica por quanto tempo ela é válida. O borrow checker garante que referências nunca apontam para dados já liberados (dangling pointers). Embora lifetimes pareçam complexos no início, eles são determinados automaticamente pelo compilador em 99% dos casos graças às "lifetime elision rules".</p>
<p>A regra essencial é: se você retorna uma referência de uma função, ela deve referenciar dados que vivem pelo menos tão longo quanto a referência. Isso previne bugs de use-after-free que são comuns em C. Entender lifetimes é fundamental para trabalhar com referências seguramente e aproveitar o poder total de Rust.</p>
<pre><code class="language-rust">fn exemplo_lifetime_simples(s: &String) -> usize {
// Lifetime implícito: a referência é válida enquanto 's' é válido
s.len()
}
fn exemplo_lifetime_explicito<'a>(s: &'a String) -> &'a str {
// Retorna slice com mesmo lifetime que 's'
&s[0..5]
}
fn demonstracao_correta() {
let texto = String::from("Lifetime em Rust");
let slice = exemplo_lifetime_explicito(&texto);
println!("{}", slice);
// 'texto' está vivo, então 'slice' é válido
}
// Isso causaria ERRO em tempo de compilação:
// fn dangling_reference() -> &'static String {
// let s = String::from("Oops");
// &s // ERRO: 's' sai de escopo, referência fica inválida
// }</code></pre>
<h2>Conclusão</h2>
<p>Stack e Heap são fundamentais para dominar Rust. O Stack oferece performance superior para dados de tamanho fixo, enquanto o Heap fornece flexibilidade para estruturas dinâmicas. O sistema de ownership elimina classes inteiras de bugs de memória ao transferir a responsabilidade para o compilador, não para o programador. Finalmente, lifetimes garantem que referências sejam sempre válidas, criando código seguro sem garbage collection.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html" target="_blank" rel="noopener noreferrer">The Rust Book - Understanding Ownership</a></li>
<li><a href="https://doc.rust-lang.org/reference/type-layout.html" target="_blank" rel="noopener noreferrer">The Rust Reference - Memory Layout</a></li>
<li><a href="https://doc.rust-lang.org/rust-by-example/std/box.html" target="_blank" rel="noopener noreferrer">Rust by Example - Stack and Heap</a></li>
<li><a href="https://github.com/rust-lang/rustlings" target="_blank" rel="noopener noreferrer">Rustlings Course - Move Semantics</a></li>
<li><a href="https://www.youtube.com/watch?v=rAl-9HwrKWs" target="_blank" rel="noopener noreferrer">Jon Gjengset - Crust of Rust: Lifetime Annotations</a></li>
</ul>