Rust

Como Usar Borrowing e Referências em Rust: & e &mut na Prática em Produção

9 min de leitura

Como Usar Borrowing e Referências em Rust: & e &mut na Prática em Produção

Entendendo o Sistema de Ownership e Borrowing O Rust resolve o problema de gerenciamento de memória através de um sistema único: o ownership. Cada valor tem um proprietário, e quando esse proprietário sai do escopo, a memória é liberada. Porém, nem sempre queremos transferir propriedade de um dado. É aí que entra o borrowing: permite usar um valor sem tomar posse dele. Existem dois tipos de referências: (imutável) e (mutável). Entender a diferença é fundamental para escrever código Rust seguro. O sistema é regido por três regras simples: você pode ter múltiplas referências imutáveis ou uma única referência mutável, mas nunca ambas simultaneamente. Isso previne data races em tempo de compilação, eliminando bugs sutis que causam problemas em outras linguagens. Referências Imutáveis (&) Uma referência imutável permite ler um valor sem modificá-lo e sem tomar posse. É como emprestar um livro para alguém ler, mas você continua sendo o dono. Você pode criar quantas referências imutáveis quiser do mesmo dado,

<h2>Entendendo o Sistema de Ownership e Borrowing</h2>

<p>O Rust resolve o problema de gerenciamento de memória através de um sistema único: o <strong>ownership</strong>. Cada valor tem um proprietário, e quando esse proprietário sai do escopo, a memória é liberada. Porém, nem sempre queremos transferir propriedade de um dado. É aí que entra o borrowing: permite usar um valor sem tomar posse dele. Existem dois tipos de referências: <code>&amp;T</code> (imutável) e <code>&amp;mut T</code> (mutável). Entender a diferença é fundamental para escrever código Rust seguro.</p>

<p>O sistema é regido por três regras simples: você pode ter múltiplas referências imutáveis <strong>ou</strong> uma única referência mutável, mas nunca ambas simultaneamente. Isso previne data races em tempo de compilação, eliminando bugs sutis que causam problemas em outras linguagens.</p>

<pre><code class="language-rust">fn main() {

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

// Referência imutável - podemos ter várias

let r1 = &amp;s;

let r2 = &amp;s;

println!(&quot;{}, {}&quot;, r1, r2); // OK: ambas usam s

// s ainda é proprietário dos dados

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

}</code></pre>

<h2>Referências Imutáveis (&amp;)</h2>

<p>Uma referência imutável <code>&amp;T</code> permite ler um valor sem modificá-lo e sem tomar posse. É como emprestar um livro para alguém ler, mas você continua sendo o dono. Você pode criar quantas referências imutáveis quiser do mesmo dado, pois ninguém está modificando nada. Quando você passa <code>&amp;s</code> para uma função, apenas a referência é passada, não os dados.</p>

<p>O ponto crítico é que enquanto existem referências imutáveis ativas, você não pode modificar o valor original. O compilador garante isso. Funções que recebem <code>&amp;T</code> não podem mudar o conteúdo, e o Rust força isso em tempo de compilação.</p>

<pre><code class="language-rust">fn calcular_comprimento(s: &amp;String) -&gt; usize {

s.len()

} // s sai do escopo, mas como não é proprietário, nada acontece

fn main() {

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

let len = calcular_comprimento(&amp;s1);

println!(&quot;&#039;{}&#039; tem comprimento {}&quot;, s1, len); // s1 ainda é válido

}</code></pre>

<p>Um exemplo prático: ao iterar sobre uma coleção em uma função, você frequentemente quer apenas ler os dados. Passar uma referência imutável evita cópia desnecessária:</p>

<pre><code class="language-rust">fn processar_nomes(nomes: &amp;Vec&lt;String&gt;) {

for nome in nomes {

println!(&quot;Nome: {}&quot;, nome);

}

}

fn main() {

let lista = vec![

String::from(&quot;Alice&quot;),

String::from(&quot;Bob&quot;),

];

processar_nomes(&amp;lista);

processar_nomes(&amp;lista); // Podemos chamar várias vezes

println!(&quot;{:?}&quot;, lista); // lista continua acessível

}</code></pre>

<h2>Referências Mutáveis (&amp;mut)</h2>

<p>Uma referência mutável <code>&amp;mut T</code> permite modificar o valor emprestado. É como emprestar o livro, mas quem o recebe tem permissão para escrever nele. A restrição crucial é <strong>uma única referência mutável por escopo</strong>: você não pode ter duas <code>&amp;mut</code> apontando para o mesmo dado simultaneamente, nem pode ter uma <code>&amp;</code> e uma <code>&amp;mut</code> juntas. Isso previne condições de corrida e modificações inesperadas.</p>

<p>Quando você passa <code>&amp;mut s</code>, está dizendo &quot;você pode modificar isso, mas é a única pessoa que pode&quot;. O compilador força essa exclusividade. Se você tentar violar as regras, terá um erro clara: &quot;cannot borrow <code>x</code> as mutable more than once&quot;.</p>

<pre><code class="language-rust">fn adicionar_exclamacao(s: &amp;mut String) {

s.push_str(&quot;!&quot;);

}

fn main() {

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

adicionar_exclamacao(&amp;mut s);

println!(&quot;{}&quot;, s); // Output: Hello!

}</code></pre>

<p>Aqui está um exemplo que falha e por quê:</p>

<pre><code class="language-rust">fn main() {

let mut x = 5;

let r1 = &amp;mut x;

let r2 = &amp;mut x; // ERRO: não pode ter duas referências mutáveis

println!(&quot;{}, {}&quot;, r1, r2);

}</code></pre>

<p>O compilador rejeita isso porque <code>r2</code> violaria a exclusividade. Porém, se você estruturar o código para que a primeira referência saia de escopo antes da segunda ser usada, funciona:</p>

<pre><code class="language-rust">fn main() {

let mut x = 5;

let r1 = &amp;mut x;

println!(&quot;{}&quot;, r1); // r1 usado aqui

let r2 = &amp;mut x; // OK: r1 não está mais em uso

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

}</code></pre>

<h3>Mixing Referências (o que NÃO fazer)</h3>

<pre><code class="language-rust">fn main() {

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

let r1 = &amp;s; // Referência imutável

let r2 = &amp;s; // Outra referência imutável

let r3 = &amp;mut s; // ERRO: não pode ter &amp;mut enquanto há &amp;

println!(&quot;{}, {}, {}&quot;, r1, r2, r3);

}</code></pre>

<h2>Boas Práticas e Padrões Comuns</h2>

<p>Rust força boas práticas através de erros de compilação. Quando você está projetando uma API, prefira <code>&amp;T</code> por padrão a menos que realmente precise modificar o valor. Use <code>&amp;mut T</code> quando a função necessita alterar seus argumentos. Para tipos que implementam <code>Copy</code> (como inteiros), às vezes não vale a pena referenciar, pois a cópia é barata.</p>

<p>Um padrão comum é usar <code>&amp;[T]</code> em vez de <code>&amp;Vec&lt;T&gt;</code> em parâmetros de função, pois slices são mais genéricas e funcionam com arrays também:</p>

<pre><code class="language-rust">fn somar_elementos(nums: &amp;[i32]) -&gt; i32 {

nums.iter().sum()

}

fn main() {

let vetor = vec![1, 2, 3, 4, 5];

let array = [10, 20, 30];

println!(&quot;Vetor: {}&quot;, somar_elementos(&amp;vetor));

println!(&quot;Array: {}&quot;, somar_elementos(&amp;array));

}</code></pre>

<p>Para modificações, use <code>&amp;mut</code> conscientemente. Se sua função modifica um vetor, deixe isso explícito na assinatura:</p>

<pre><code class="language-rust">fn limpar_espacos(s: &amp;mut String) {

s.retain(|c| !c.is_whitespace());

}

fn main() {

let mut texto = String::from(&quot;Hello World&quot;);

limpar_espacos(&amp;mut texto);

println!(&quot;{}&quot;, texto); // Output: HelloWorld

}</code></pre>

<h2>Conclusão</h2>

<p>O sistema de borrowing em Rust é seu maior trunfo para evitar bugs de memória. Lembre-se: <strong>uma única referência mutável ou múltiplas imutáveis, nunca ambas</strong>. Use <code>&amp;T</code> para leitura e <code>&amp;mut T</code> apenas quando necessário modificar. Essa aparente restrição é na verdade liberdade — liberdade de não se preocupar com data races, use-after-free ou double-frees. Dominando referências, você domina Rust.</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 - Ownership</a></li>

<li><a href="https://doc.rust-lang.org/book/ch04-02-references-and-borrowing.html" target="_blank" rel="noopener noreferrer">The Rust Book - References and Borrowing</a></li>

<li><a href="https://doc.rust-lang.org/rust-by-example/scope/borrow.html" target="_blank" rel="noopener noreferrer">Rust by Example - Borrowing</a></li>

<li><a href="https://doc.rust-lang.org/std/" target="_blank" rel="noopener noreferrer">Official Rust Standard Library Documentation</a></li>

<li><a href="https://www.oreilly.com/library/view/programming-rust/9781491927281/" target="_blank" rel="noopener noreferrer">Programming Rust: Fast, Safe Systems Development - Jim Blandy &amp; Jason Orendorff</a></li>

</ul>

Comentários

Mais em Rust

Generics em Rust: Funções e Structs Parametrizadas por Tipo: Do Básico ao Avançado
Generics em Rust: Funções e Structs Parametrizadas por Tipo: Do Básico ao Avançado

Introdução aos Generics em Rust Generics são um dos pilares da programação mo...

Dominando Variáveis, Mutabilidade e Tipos Primitivos em Rust em Projetos Reais
Dominando Variáveis, Mutabilidade e Tipos Primitivos em Rust em Projetos Reais

Variáveis e Imutabilidade em Rust Em Rust, toda variável é imutável por padrã...

Traits em Rust: Definindo Comportamento Compartilhado: Do Básico ao Avançado
Traits em Rust: Definindo Comportamento Compartilhado: Do Básico ao Avançado

Introdução: O que são Traits? Traits em Rust são abstrações poderosas que per...