---
title: "Ownership e Borrowing em Rust: Tutorial | Rust Brasil"
url: "https://rustlang.com.br/tutoriais/ownership-borrowing/"
markdown_url: "https://rustlang.com.br/tutoriais/ownership-borrowing.MD"
description: "Entenda ownership, borrowing e lifetimes em Rust. Tutorial completo com exemplos práticos do sistema de propriedade."
date: "2026-02-21"
author: "Equipe Rust Brasil"
---

# Ownership e Borrowing em Rust: Tutorial | Rust Brasil

Entenda ownership, borrowing e lifetimes em Rust. Tutorial completo com exemplos práticos do sistema de propriedade.


Se existe um conceito que define o Rust e o diferencia de todas as outras linguagens, é o **sistema de ownership** (propriedade). É graças a esse sistema que o Rust consegue garantir segurança de memória sem precisar de um garbage collector. Neste tutorial, vamos desmistificar esse conceito fundamental com muitos exemplos e diagramas.

## Por que Ownership Existe?

Em linguagens como C/C++, o programador gerencia a memória manualmente, o que leva a bugs como use-after-free, double-free e memory leaks. Em linguagens como Java/Python/Go, um garbage collector faz esse trabalho, mas com custo de performance.

Rust encontrou uma terceira via: o **sistema de ownership** verifica regras de memória **em tempo de compilação**. Se o código viola essas regras, ele simplesmente não compila. Isso significa:

- Zero custo em runtime (sem garbage collector)
- Impossível ter use-after-free, double-free ou data races
- Erros de memória são pegos pelo compilador, não em produção

## As Três Regras do Ownership

Memorize estas três regras — todo o sistema se baseia nelas:

1. **Cada valor em Rust tem uma variável que é sua "dona" (owner)**
2. **Só pode haver um owner por vez**
3. **Quando o owner sai do escopo, o valor é descartado (dropped)**

Vamos ver cada regra na prática.

### Regra 1: Todo valor tem um owner

```rust
fn main() {
    let nome = String::from("Rust Brasil"); // 'nome' é o owner de "Rust Brasil"
    println!("{}", nome);
} // 'nome' sai do escopo aqui, a String é descartada
```

Diagrama da memória:

```
  Stack                Heap
  +--------+          +---+---+---+---+---+---+---+---+---+---+---+
  | nome   | -------> | R | u | s | t |   | B | r | a | s | i | l |
  | ptr    |          +---+---+---+---+---+---+---+---+---+---+---+
  | len: 11|
  | cap: 11|
  +--------+
```

A variável `nome` na stack contém um ponteiro para os dados na heap, o tamanho e a capacidade. Quando `nome` sai do escopo, tanto a stack quanto a heap são liberadas.

### Regra 2: Apenas um owner por vez (Move Semantics)

Quando você atribui uma `String` a outra variável, o ownership é **movido** (moved):

```rust
fn main() {
    let s1 = String::from("Olá");
    let s2 = s1;  // s1 é MOVIDO para s2

    // println!("{}", s1); // ERRO! s1 não é mais válido!
    println!("{}", s2);    // OK! s2 é o novo owner
}
```

Diagrama do que acontece:

```
  ANTES do move:              DEPOIS do move:

  Stack         Heap          Stack         Heap
  +------+     +---+---+---+  +------+     +---+---+---+
  | s1   |---->| O | l | á |  | s1   | (inválido)      |
  +------+     +---+---+---+  +------+     +---+---+---+
                              | s2   |---->| O | l | á |
                              +------+     +---+---+---+
```

Isso é radicalmente diferente de outras linguagens! Em Python, `s2 = s1` criaria duas referências para o mesmo objeto. Em C++, faria uma cópia (deep copy). Em Rust, **transfere a propriedade**.

Por que? Porque se `s1` e `s2` apontassem para o mesmo dado na heap, quando ambos saíssem do escopo, o Rust tentaria liberar a mesma memória duas vezes (double-free). O move previne isso.

### Regra 3: Drop automático ao sair do escopo

```rust
fn main() {
    {
        let texto = String::from("Temporário");
        println!("{}", texto);
    } // 'texto' é descartado aqui automaticamente

    // println!("{}", texto); // ERRO! 'texto' não existe mais
}
```

O Rust chama automaticamente a função `drop` quando uma variável sai do escopo. Isso é similar ao RAII do C++, mas obrigatório e verificado pelo compilador.

## Move em Funções

Passar um valor para uma função também transfere o ownership:

```rust
fn imprimir(texto: String) {
    println!("{}", texto);
} // 'texto' é descartado aqui

fn main() {
    let mensagem = String::from("Olá, Rust!");
    imprimir(mensagem);  // ownership movido para a função

    // println!("{}", mensagem); // ERRO! 'mensagem' foi movido
}
```

Diagrama do fluxo:

```
  main()                    imprimir()
  +----------+              +----------+
  | mensagem |--- move ---->| texto    |
  | (movido) |              | (owner)  |
  +----------+              +----------+
                                  |
                            drop ao sair
                            do escopo
```

O mesmo acontece com retorno de funções — o ownership é transferido de volta:

```rust
fn criar_saudacao(nome: &str) -> String {
    format!("Olá, {}!", nome)  // retorna um novo String
}

fn main() {
    let saudacao = criar_saudacao("Maria");
    // 'saudacao' é o owner da String retornada
    println!("{}", saudacao);
}
```

## Tipos que Implementam Copy

Tipos simples que vivem inteiramente na stack implementam o trait `Copy`. Para esses tipos, atribuição cria uma **cópia** em vez de um move:

```rust
fn main() {
    let x = 42;    // i32 implementa Copy
    let y = x;     // x é COPIADO, não movido
    println!("x = {}, y = {}", x, y); // Ambos são válidos!

    let a = true;  // bool implementa Copy
    let b = a;     // cópia
    println!("a = {}, b = {}", a, b); // OK!

    let c = 3.14;  // f64 implementa Copy
    let d = c;     // cópia
    println!("c = {}, d = {}", c, d); // OK!
}
```

Tipos que implementam `Copy`: todos os inteiros, floats, `bool`, `char`, tuplas (se todos os elementos forem Copy), arrays (se o tipo do elemento for Copy), e referências `&T`.

Tipos que **NÃO** implementam `Copy`: `String`, `Vec<T>`, `Box<T>`, e qualquer tipo que aloca na heap.

## Clone: Cópia Explícita

Quando você precisa de uma cópia profunda de um tipo que não implementa `Copy`, use `clone()`:

```rust
fn main() {
    let s1 = String::from("Olá");
    let s2 = s1.clone();  // cópia profunda explícita

    println!("s1 = {}", s1); // OK! s1 ainda é válido
    println!("s2 = {}", s2); // OK! s2 é uma cópia independente
}
```

Diagrama com clone:

```
  Stack          Heap
  +------+      +---+---+---+
  | s1   |----->| O | l | á |  (dados originais)
  +------+      +---+---+---+

  +------+      +---+---+---+
  | s2   |----->| O | l | á |  (cópia independente)
  +------+      +---+---+---+
```

Use `clone()` com consciência — ele pode ser custoso para dados grandes. Na maioria dos casos, o borrowing é a solução melhor.

## Borrowing (Empréstimo): Referências

E se você quiser usar um valor sem tomar o ownership? É aí que entra o **borrowing** (empréstimo). Você cria uma **referência** ao valor:

```rust
fn calcular_tamanho(texto: &String) -> usize {
    texto.len()
} // 'texto' sai do escopo, mas como é uma referência, não faz drop

fn main() {
    let mensagem = String::from("Olá, Rust Brasil!");
    let tamanho = calcular_tamanho(&mensagem);

    // 'mensagem' ainda é válida!
    println!("'{}' tem {} bytes", mensagem, tamanho);
}
```

Diagrama:

```
  main()                    calcular_tamanho()
  +-----------+             +--------+
  | mensagem  |<---ref------| texto  |  (&String)
  | (owner)   |             | (ref)  |
  +-----------+             +--------+
  | ptr  -----|---> Heap         |
  | len: 18   |              lê, mas não
  | cap: 18   |              modifica nem
  +-----------+              toma ownership
```

O `&` cria uma referência. Ao chamar a função com `&mensagem`, estamos **emprestando** o valor, não transferindo ownership.

### Referências Mutáveis

Para modificar um valor emprestado, use `&mut`:

```rust
fn adicionar_exclamacao(texto: &mut String) {
    texto.push('!');
}

fn main() {
    let mut mensagem = String::from("Olá, Rust");
    println!("Antes: {}", mensagem);

    adicionar_exclamacao(&mut mensagem);
    println!("Depois: {}", mensagem);
}
```

Saída:

```
Antes: Olá, Rust
Depois: Olá, Rust!
```

### As Regras do Borrowing

O borrow checker do Rust impõe duas regras fundamentais:

**Regra 1: Pode haver quantas referências imutáveis (`&T`) forem necessárias ao mesmo tempo:**

```rust
fn main() {
    let texto = String::from("Olá");

    let r1 = &texto;  // OK
    let r2 = &texto;  // OK
    let r3 = &texto;  // OK

    println!("{}, {}, {}", r1, r2, r3); // Todas válidas
}
```

**Regra 2: Só pode haver UMA referência mutável (`&mut T`) por vez, e nenhuma referência imutável pode coexistir com ela:**

```rust
fn main() {
    let mut texto = String::from("Olá");

    let r1 = &mut texto;
    // let r2 = &mut texto;     // ERRO! Duas refs mutáveis
    // let r3 = &texto;         // ERRO! Ref imutável com ref mutável
    r1.push_str(", mundo!");
    println!("{}", r1);
}
```

Diagrama das regras:

```
  +-----------------------------------------------------+
  |  PERMITIDO:                                         |
  |                                                      |
  |  &T + &T + &T    (muitas refs imutáveis)            |
  |  &mut T           (uma ref mutável, sozinha)         |
  |                                                      |
  +-----------------------------------------------------+
  |  PROIBIDO:                                           |
  |                                                      |
  |  &mut T + &mut T  (duas refs mutáveis)              |
  |  &mut T + &T      (mutável com imutável)            |
  |                                                      |
  +-----------------------------------------------------+
```

Por que essa restrição? Ela previne **data races** em tempo de compilação. Um data race ocorre quando dois acessos ao mesmo dado acontecem simultaneamente e pelo menos um é de escrita.

### Non-Lexical Lifetimes (NLL)

O compilador Rust é inteligente o suficiente para saber quando uma referência não está mais sendo usada:

```rust
fn main() {
    let mut texto = String::from("Olá");

    let r1 = &texto;
    let r2 = &texto;
    println!("{} e {}", r1, r2);
    // r1 e r2 não são mais usados depois daqui

    let r3 = &mut texto;  // OK! r1 e r2 já não estão em uso
    r3.push_str(", mundo!");
    println!("{}", r3);
}
```

Isso funciona porque o compilador detecta que `r1` e `r2` não são usados depois do primeiro `println!`, então seus "lifetimes" terminam ali.

## Lifetimes: Uma Introdução

Lifetimes são a forma do Rust garantir que referências nunca apontem para dados inválidos. Na maioria dos casos, o compilador infere os lifetimes automaticamente. Mas às vezes você precisa anotá-los:

```rust
// O compilador não sabe qual referência retornar
// Precisamos dizer que o retorno vive tanto quanto ambos os parâmetros
fn maior<'a>(s1: &'a str, s2: &'a str) -> &'a str {
    if s1.len() > s2.len() {
        s1
    } else {
        s2
    }
}

fn main() {
    let string1 = String::from("Olá, mundo!");
    let resultado;

    {
        let string2 = String::from("Olá");
        resultado = maior(&string1, &string2);
        println!("A maior string é: {}", resultado);
    }
    // Se tentássemos usar 'resultado' aqui, e ele fosse &string2,
    // teríamos uma referência inválida. O lifetime 'a garante segurança.
}
```

O `'a` é uma **anotação de lifetime**. Ela diz ao compilador: "o retorno desta função vive pelo menos tanto quanto os parâmetros". Não se preocupe em dominar lifetimes agora — eles serão abordados com mais detalhes em tutoriais avançados.

## Dangling References: O Que Rust Impede

Uma "dangling reference" é uma referência para memória que já foi liberada. Rust **impede** isso em tempo de compilação:

```rust
// ERRO! Não compila!
// fn criar_referencia() -> &String {
//     let s = String::from("Olá");
//     &s  // s será descartada, a referência seria inválida!
// }

// CORRETO: retorne o valor por ownership
fn criar_string() -> String {
    let s = String::from("Olá");
    s  // ownership é transferido para quem chamou
}

fn main() {
    let texto = criar_string();
    println!("{}", texto);
}
```

## Exemplo Prático: Sistema de Biblioteca

Vamos aplicar tudo que aprendemos em um exemplo real:

```rust
fn contar_palavras(texto: &str) -> usize {
    texto.split_whitespace().count()
}

fn primeira_palavra(texto: &str) -> &str {
    let bytes = texto.as_bytes();
    for (i, &byte) in bytes.iter().enumerate() {
        if byte == b' ' {
            return &texto[0..i];
        }
    }
    texto
}

fn formatar_titulo(titulo: &mut String) {
    let primeiro_char = titulo.remove(0).to_uppercase().to_string();
    titulo.insert_str(0, &primeiro_char);
    titulo.push('.');
}

fn gerar_resumo(titulo: &str, autor: &str, paginas: u32) -> String {
    format!(
        "\"{}\" por {} ({} páginas, ~{} palavras estimadas)",
        titulo,
        autor,
        paginas,
        paginas * 250
    )
}

fn main() {
    // Ownership: livro_titulo é o owner
    let mut livro_titulo = String::from("a linguagem de programação Rust");

    // Borrowing imutável: contar palavras sem tomar ownership
    let palavras = contar_palavras(&livro_titulo);
    println!("Palavras no título: {}", palavras);

    // Borrowing imutável: pegar primeira palavra
    let primeira = primeira_palavra(&livro_titulo);
    println!("Primeira palavra: {}", primeira);

    // Borrowing mutável: formatar o título
    formatar_titulo(&mut livro_titulo);
    println!("Título formatado: {}", livro_titulo);

    // Ownership: gerar_resumo recebe referências e retorna um novo String
    let resumo = gerar_resumo(&livro_titulo, "Steve Klabnik", 560);
    println!("\n{}", resumo);

    // Clone: quando realmente precisa de uma cópia
    let titulo_backup = livro_titulo.clone();
    println!("\nOriginal: {}", livro_titulo);
    println!("Backup: {}", titulo_backup);
}
```

Saída:

```
Palavras no título: 5
Primeira palavra: a
Título formatado: A linguagem de programação Rust.

"A linguagem de programação Rust." por Steve Klabnik (560 páginas, ~140000 palavras estimadas)

Original: A linguagem de programação Rust.
Backup: A linguagem de programação Rust.
```

## Resumo Visual das Regras

```
  +================================================================+
  |                    OWNERSHIP EM RUST                            |
  +================================================================+
  |                                                                 |
  |  let s1 = String::from("Olá");                                |
  |  let s2 = s1;              // MOVE: s1 invalidado             |
  |  let s3 = s2.clone();      // CLONE: cópia independente       |
  |                                                                 |
  |  let x = 42;                                                   |
  |  let y = x;                // COPY: tipos simples copiam      |
  |                                                                 |
  +----------------------------------------------------------------+
  |                     BORROWING                                   |
  +----------------------------------------------------------------+
  |                                                                 |
  |  let r1 = &s2;            // ref imutável (pode ter várias)   |
  |  let r2 = &s2;            // outra ref imutável (OK)          |
  |                                                                 |
  |  let r3 = &mut s2;        // ref mutável (apenas uma)         |
  |                            // NÃO pode coexistir com &s2      |
  |                                                                 |
  +================================================================+
```

## Dicas Para Lidar com o Borrow Checker

1. **Comece com referências** (`&` e `&mut`) antes de recorrer a `.clone()`
2. **Use `.clone()` sem culpa** enquanto aprende — otimize depois
3. **Preste atenção nas mensagens de erro** — o compilador do Rust é excepcionalmente útil
4. **Pense em quem é o "dono"** de cada dado no seu programa
5. **Funções que só leem** devem receber `&T`; funções que modificam, `&mut T`

## Próximos Passos

Agora que você entende ownership e borrowing, está pronto para aprender a modelar dados de forma expressiva com structs, enums e pattern matching.

Acesse o tutorial [Structs, Enums e Pattern Matching](/tutoriais/structs-enums-pattern-matching/) para continuar.

---

Cada linguagem tem sua abordagem para gerenciamento de memória — compare:

- <a href="https://ziglang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'ziglang.com.br' })">Gerenciamento de memória em Zig</a> — controle manual explícito com allocators e sem garbage collector
- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Modelo de GC do Go</a> — garbage collector de baixa latência como alternativa ao ownership
