Rust

Guia Completo de Stack vs Heap em Rust: Como a Memória é Gerenciada

9 min de leitura

Guia Completo de Stack vs Heap em Rust: Como a Memória é Gerenciada

Stack vs Heap em Rust: Como a Memória é Gerenciada O que são Stack e Heap? 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. 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 , , , vivem no Heap com um ponteiro no Stack. Compreender essa divisão é essencial

<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(&quot;Rust&quot;); // Dados &quot;Rust&quot; no Heap, ponteiro em s1 no Stack

let s2 = s1; // Ownership transferido, s1 não é mais válido

println!(&quot;x = {}, y = {}, s2 = {}&quot;, x, y, s2);

// println!(&quot;{}&quot;, 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>&amp;T</code>) e mutáveis (<code>&amp;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(&quot;Hello&quot;);

let s2 = s1; // Ownership movido para s2

// println!(&quot;{}&quot;, s1); // ERRO: s1 já não é proprietário

println!(&quot;{}&quot;, s2); // OK: s2 é o proprietário agora

}

fn usando_referencias() {

let s1 = String::from(&quot;Rust&quot;);

let len = calcular_tamanho(&amp;s1); // Referência imutável

println!(&quot;Tamanho de &#039;{}&#039;: {}&quot;, s1, len);

}

fn calcular_tamanho(s: &amp;String) -&gt; usize {

s.len()

// &#039;s&#039; sai de escopo aqui, mas não libera memória

// porque não é proprietário, apenas referencia

}

fn modificar_string(s: &amp;mut String) {

s.push_str(&quot; é incrível&quot;);

}

fn usando_mutabilidade() {

let mut s = String::from(&quot;Rust&quot;);

modificar_string(&amp;mut s); // Referência mutável

println!(&quot;{}&quot;, s); // &quot;Rust é incrível&quot;

}</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!(&quot;a: {}, b: {}&quot;, a, b); // Ambos válidos

// HEAP: move (String não implementa Copy)

let s1 = String::from(&quot;Stack?&quot;);

let s2 = s1; // Move eficiente: só o ponteiro foi transferido

// println!(&quot;{}&quot;, s1); // ERRO

// Clone explícito (caro)

let s3 = String::from(&quot;Clone&quot;);

let s4 = s3.clone(); // Cópia custosa: String inteira duplicada no Heap

println!(&quot;s3: {}, s4: {}&quot;, 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!(&quot;{:?}&quot;, v2);

// println!(&quot;{:?}&quot;, 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 &quot;tempo de vida&quot; de uma referência através de <strong>lifetimes</strong>. Toda referência tem um lifetime (anotado com <code>&#039;a</code>, <code>&#039;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 &quot;lifetime elision rules&quot;.</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: &amp;String) -&gt; usize {

// Lifetime implícito: a referência é válida enquanto &#039;s&#039; é válido

s.len()

}

fn exemplo_lifetime_explicito&lt;&#039;a&gt;(s: &amp;&#039;a String) -&gt; &amp;&#039;a str {

// Retorna slice com mesmo lifetime que &#039;s&#039;

&amp;s[0..5]

}

fn demonstracao_correta() {

let texto = String::from(&quot;Lifetime em Rust&quot;);

let slice = exemplo_lifetime_explicito(&amp;texto);

println!(&quot;{}&quot;, slice);

// &#039;texto&#039; está vivo, então &#039;slice&#039; é válido

}

// Isso causaria ERRO em tempo de compilação:

// fn dangling_reference() -&gt; &amp;&#039;static String {

// let s = String::from(&quot;Oops&quot;);

// &amp;s // ERRO: &#039;s&#039; 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>

Comentários

Mais em Rust

Dominando Slices em Rust: Referências para Partes de Coleções em Projetos Reais
Dominando Slices em Rust: Referências para Partes de Coleções em Projetos Reais

Entendendo Slices em Rust Um slice é uma referência a uma parte contígua de u...

Como Usar Enums em Rust: Definição, Variantes e Dados Associados em Produção
Como Usar Enums em Rust: Definição, Variantes e Dados Associados em Produção

Introdução: O Que São Enums em Rust? Enums (enumerações) são um dos pilares d...

O que Todo Dev Deve Saber sobre Biblioteca anyhow: Tratamento de Erros em Aplicações Rust
O que Todo Dev Deve Saber sobre Biblioteca anyhow: Tratamento de Erros em Aplicações Rust

Por Que Tratamento de Erros em Rust é Diferente Em Rust, erros não são exceçõ...