Skip to main content
📝 Rust application development

Dominando o sistema de propriedade do Rust: o guia completo para segurança da memória sem coleta de lixo

Master Rust ownership, borrowing, and lifetimes. The complete guide to memory safety without garbage collection — with practical code examples.

23 min

Tempo de leitura

4,590

Palavras

Oct 30, 2025

Publicado

Engr Mejba Ahmed

Escrito por

Engr Mejba Ahmed

Compartilhar Artigo

Dominando o sistema de propriedade do Rust: o guia completo para segurança da memória sem coleta de lixo

Dominando o sistema de propriedade do Rust: o guia completo para segurança da memória sem coleta de lixo


1. Por que a propriedade é importante: o problema de US$ 2 trilhões

Bugs de segurança de memória custam à indústria de software cerca de US$ 2 trilhões anualmente. A Microsoft relata que 70% de suas vulnerabilidades de segurança são problemas de segurança de memória. A equipe do Google Chrome encontrou números semelhantes. Esses bugs incluem:

  • Use-after-free: Acessando a memória que foi desalocada
  • Double-free: Liberando memória duas vezes, causando corrupção
  • Estouros de buffer: gravação além da memória alocada
  • Corridas de dados: acesso simultâneo causando comportamento imprevisível
  • Vazamentos de memória: Esquecimento de liberar memória alocada

A compensação tradicional

As linguagens de programação historicamente escolheram uma de duas abordagens:

Abordagem 1: gerenciamento manual de memória (C/C++)

// Código C - sujeito a erros
char* criar_mensagem() {
    char* mensagem = malloc(100);
    strcpy(mensagem, "Olá");
    retornar mensagem;  // O chamador deve se lembrar de liberar!
}

processo vazio() {
    char* m = criar_mensagem();
    printf("%s",m);
    // Esqueci de free(m) - vazamento de memória!
}

Problemas: Requer disciplina perfeita, facilidade de cometer erros, vulnerabilidades de segurança.

Abordagem 2: Coleta de lixo (Java/Go/JavaScript)

// Código Java - seguro, mas com sobrecarga de tempo de execução
String createMessage() {
    retorne "Olá";  // O GC irá limpar eventualmente
}
// Seguro, mas as pausas do GC afetam o desempenho

Problemas: pausas imprevisíveis, sobrecarga de memória, menos controle sobre o desempenho.

A solução revolucionária de Rust

Rust fornece uma terceira via: segurança de memória sem coleta de lixo por meio de verificação de propriedade em tempo de compilação. Você obtém:

  • ✅ Segurança de memória garantida em tempo de compilação
  • ✅ Sem sobrecarga de tempo de execução (abstrações de custo zero)
  • ✅ Sem pausas no coletor de lixo
  • ✅ Simultaneidade destemida (corridas de dados impossíveis)
  • ✅ Desempenho previsível

O problema? Você deve aprender o sistema de propriedade. Este guia deixará isso bem claro.


2. As três regras de ouro da propriedade

Todo programa Rust segue estas três regras, aplicadas em tempo de compilação:

Regra 1: Cada valor tem um único proprietário

fn principal() {
    deixe s = String::from("olá");  //s possui a String
    // Apenas uma variável pode possuir esses dados por vez
} // s sai do escopo, a memória é liberada automaticamente

Regra 2: Quando o proprietário sai do escopo, o valor é eliminado

fn principal() {
    {
        deixe s = String::from("olá");  // s é válido daqui
        //faz coisas com s
    } // s sai do escopo e é descartado, memória liberada

    //println!("{}",s);  //ERRO: s não existe mais
}

Regra 3: Você pode ter uma referência mutável OU múltiplas referências imutáveis

fn principal() {
    deixe mut s = String::from("olá");

    // Múltiplas referências imutáveis - OK
    seja r1 = &s;
    seja r2 = &s;
    println!("{} e {}", r1, r2);

    // Uma referência mutável - OK (após r1, r2 não são mais usados)
    seja r3 = &mut s;
    r3.push_str("mundo");
    println!("{}", r3);
}

Por que essas regras?

  • Regra 1 e 2: Evita vazamentos de memória e erros de liberação dupla
  • Regra 3: Evita corridas de dados em tempo de compilação

3. Mover Semântica: Compreendendo a Transferência de Propriedade

O problema: a cópia ingênua é cara

fn principal() {
    deixe s1 = String::from("olá");
    seja s2 = s1;  // O que acontece aqui?

    // println!("{}", s1);  //ERRO: valor movido para s2
}

O que realmente acontece:

Pilha: Pilha:
s1 -> [ptr, len, cap] -> dados "olá"
      |
      | (mover)
      v
s2 -> [ptr, len, cap] -> (mesmos dados de heap)

Rust muda propriedade em vez de copiar dados heap. Depois de let s2 = s1, apenas s2 é válido. Isso evita:

  • Cópias profundas caras por padrão
  • Dupla liberação de erros (somente s2 liberará memória)

Quando o Rust copia em vez de mover?

Os tipos que implementam a característica Copy são copiados em vez de movidos:

fn principal() {
    // Inteiros, floats, bools, chars implementam Copy
    seja x = 5;
    seja y = x;  // x é copiado para y
    println!("x = {}, y = {}", x, y);  // Ambos válidos!

    // Tuplas de tipos Copy também são Copy
    seja ponto = (3, 4);
    deixe ponto2 = ponto;  //copiado
    println!("{:?} e {:?}", ponto, ponto2);  // Ambos válidos!
}

Regra geral: Se um tipo armazena dados no heap ou possui recursos, ele não implementará Copy.

Clonagem explícita quando você precisar

fn principal() {
    deixe s1 = String::from("olá");
    deixe s2 = s1.clone();  // Cópia profunda explícita

    println!("s1 = {}, s2 = {}", s1, s2);  // Ambos válidos
}

Use clonar quando:

  • Você precisa de cópias independentes
  • O custo de desempenho é aceitável
  • Deixa a intenção clara

Evite clonar quando:

  • Você pode reestruturar para usar empréstimos
  • O desempenho é crítico
  • Trabalhando em loops quentes

4. Empréstimo: referências que não pertencem

O empréstimo permite que você faça referência a dados sem assumir a propriedade.

Referências imutáveis (&T)

fn principal() {
    deixe s = String::from("olá");

    deixe len = calcular_comprimento(&s);  // Pegar emprestado

    println!("O comprimento de '{}' é {}", s, len);  // ainda é válido!
}

fn calcular_comprimento(s: &String) -> usar {
    s.len()
} // s sai do escopo, mas não possui os dados, então nada acontece

Pontos-chave:

  • &s cria uma referência para s
  • As referências são imutáveis por padrão
  • Múltiplas referências imutáveis ​​permitidas
  • O proprietário original ainda pode ler os dados

Referências mutáveis (&mut T)

fn principal() {
    deixe mut s = String::from("olá");

    alterar(&mut s);  // Pegar emprestado de forma mutável

    println!("{}",s);  // Imprime "olá, mundo"
}

fn mudança(s: &mut String) {
    s.push_str(", mundo");
}

Restrição crítica: Apenas uma referência mutável por vez!

fn principal() {
    deixe mut s = String::from("olá");

    seja r1 = &mut s;
    seja r2 = &mut s;  // ERRO: não é possível emprestar como mutável mais de uma vez

    println!("{}, {}", r1, r2);
}

Por quê? Evita corridas de dados em tempo de compilação:

// Isso é impossível em Rust (compilaria em C++)
deixe mut data = vec![1, 2, 3];
deixe ref1 = &mut dados;
deixe ref2 = &mut dados;
ref1.push(4);        //Pode realocar
ref2.push(5);        // Poderia causar uso após livre!

O segredo do verificador de empréstimos: tempos de vida não lexicais (NLL)

Modern Rust (edição 2018+) é mais inteligente sobre quando as referências terminam:

fn principal() {
    deixe mut s = String::from("olá");

    seja r1 = &s;
    seja r2 = &s;
    println!("{} e {}", r1, r2);
    // r1 e r2 não são mais usados após este ponto

    seja r3 = &mut s;  // OK! r1 e r2 estão "mortos"
    r3.push_str("mundo");
    println!("{}", r3);
}

Antes do NLL (edição de 2015), isso não seria compilado. Agora o compilador rastreia onde as referências são realmente usadas, não apenas seu escopo léxico.

Padrão de empréstimo comum: leituras múltiplas, gravação única

fn principal() {
    deixe mut data = vec![1, 2, 3, 4, 5];

    // Fase de leitura: múltiplos empréstimos imutáveis
    deixe primeiro = &data[0];
    deixe último = &data[data.len() - 1];
    println!("primeiro: {}, último: {}", primeiro, último);

    // Fase de gravação: empréstimo mutável exclusivo
    dados.push(6);
    println!("Atualizado: {:?}", dados);
}

5. Vidas: ensinando o compilador sobre referências

A solução do problema ao longo da vida

fn mais longo(x: &str, y: &str) -> &str {
    se x.len() > y.len() {
        x // Qual tempo de vida o retorno deve ter?
    } senão {
        y // tempo de vida de x ou tempo de vida de y?
    }
}

O compilador não pode determinar se a referência retornada é válida:

fn principal() {
    deixe string1 = String::from("string longa");
    deixe o resultado;

    {
        deixe string2 = String::from("curto");
        resultado = mais longo(&string1, &string2);
    } // string2 descartada aqui

    // O resultado é válido? Depende de qual entrada foi retornada!
}

Anotações vitalícias: contratos explícitos

fn mais longo<'a>(x: &'a str, y: &'a str) -> &'a str {
    se x.len() > y.len() {
        x
    } senão {
        sim
    }
}

Lendo isto: "A referência retornada será válida desde que ambos x e y sejam válidos."

fn principal() {
    deixe string1 = String::from("string longa");
    deixe o resultado;

    {
        deixe string2 = String::from("curto");
        resultado = mais longo(&string1, &string2);
        println!("{}", resultado);  // OK: ambos ainda válidos
    } // string2 descartada

    // println!("{}", resultado);  // ERRO: string2 pode ter sido retornado
}

Elisão vitalícia: quando você não precisa de anotações

O compilador pode inferir tempos de vida em padrões comuns:

// Nenhuma anotação necessária - tempo de vida de entrada única
fn primeira_palavra(s: &str) -> &str {
    s.split_whitespace().next().unwrap_or("")
}

// O compilador vê isso como:
fn primeira_palavra<'a>(s: &'a str) -> &'a str {
    s.split_whitespace().next().unwrap_or("")
}

Regras de Elisão:

  1. Cada referência de entrada tem seu próprio tempo de vida
  2. Se houver exatamente um tempo de vida de entrada, a saída obtém esse tempo de vida
  3. Se o método for &self, a saída obtém o tempo de vida de self

Vidas em estruturas

Quando estruturas contêm referências, você deve anotar os tempos de vida:

struct ImportanteExcerpt<'a> {
    parte: &'a str,
}

fn principal() {
    let novel = String::from("Me chame de Ismael. Alguns anos atrás...");
    deixe first_sentence = novel.split('.').next().unwrap();

    deixe trecho = ImportanteExcerto {
        parte: primeira_frase,
    };

    println!("{}", trecho.part);
} // trecho e romance descartados, tudo bem

Significado: Um ImportantExcerpt não pode sobreviver aos dados aos quais faz referência.

fn principal() {
    deixe trecho;

    {
        let novel = String::from("Me chame de Ismael.");
        trecho = ImportanteExcerto {
            parte: &romance,
        };
    } // ERRO: romance descartado, excerpt.part ficaria pendurado

    // println!("{}", trecho.part);
}

A 'vida estática

'static significa "vive durante toda a duração do programa":

// Literais de string têm 'tempo de vida estático
vamos: &'static str = "Estou armazenado no binário";

//Variáveis estáticas
estático GLOBAL: &str = "Também 'estático";

Erro comum: Não use 'static apenas para fazer com que os erros desapareçam!

// RUIM: Forçando 'estático para compilar
fn bad_function() -> &'static str {
    deixe s = String::from("olá");
    // &s // Não pode retornar - não vive o suficiente
    // Vazamento de memória para obter 'estático está errado!
}

// BOM: Em vez disso, retorne os dados de propriedade
fn good_function() -> String {
    String::from("olá")
}

6. Armadilhas comuns e como corrigi-las

Armadilha 1: Não é possível emprestar como mutável porque já foi emprestado

//ERRO
fn principal() {
    deixe mut vec = vec![1, 2, 3];

    deixe primeiro = &vec[0];  //Empréstimo imutável
    vec.push(4);          // ERRO: empréstimo mutável
    println!("{}", primeiro);
}

Por que falha: push pode ser realocado, invalidando first.

Solução 1: Reestruturação para separar empréstimos

fn principal() {
    deixe mut vec = vec![1, 2, 3];

    deixe first_value = vec[0];  // Copie o valor
    vec.push(4);               // OK: nenhum empréstimo pendente
    println!("{}", primeiro_valor);
}

Solução 2: clonar os dados antes da mutação

fn principal() {
    deixe mut vec = vec![1, 2, 3];

    deixe primeiro = vec.get(0).cloned();  // Opção<i32>, sem empréstimo
    vec.push(4);
    se deixar Some(val) = primeiro {
        println!("{}", val);
    }
}

Armadilha 2: Não é possível retornar referência à variável local

//ERRO
fn criar_string() -> &String {
    deixe s = String::from("olá");
    &s // ERRO: retorna referência aos dados pertencentes à função
} // s é descartado aqui, retornaria um ponteiro pendente!

Solução: retornar dados próprios

fn criar_string() -> String {
    String::from("hello") // Propriedade transferida para o chamador
}

Armadilha 3: Não é possível sair do conteúdo emprestado

//ERRO
fn principal() {
    deixe vec = vec![String::from("a"), String::from("b")];
    deixe primeiro = &vec;

    vamos levar = vec[0];  // ERRO: não é possível sair do conteúdo indexado
}

Solução 1: clonar o valor

fn principal() {
    deixe vec = vec![String::from("a"), String::from("b")];
    vamos levar = vec[0].clone();
    println!("{}", obtido);
}

Solução 2: use métodos que transferem propriedade

fn principal() {
    deixe mut vec = vec![String::from("a"), String::from("b")];
    vamos levar = vec.swap_remove(0);  // Assume a propriedade
    println!("{}", obtido);
}

Armadilha 4: Empréstimos Simultâneos Mutáveis e Imutáveis

//ERRO
fn principal() {
    deixe mut map = HashMap::new();
    map.insert("chave", "valor");

    deixe valor = map.get("chave");
    map.insert("chave2", "valor2");  // ERRO: não é possível sofrer mutação enquanto estiver emprestado
    println!("{:?}", valor);
}

Solução: use a API de entrada

fn principal() {
    deixe mut map = HashMap::new();
    map.insert("chave", "valor");

    map.entry("chave2").or_insert("valor2");  // Sem empréstimos conflitantes

    if let Some(value) = map.get("key") {
        println!("{}", valor);
    }
}

Armadilha 5: Incompatibilidade vitalícia em estruturas

//ERRO
estrutura Contêiner {
    dados: &str, // ERRO: anotação de tempo de vida ausente
}

Solução: adicionar parâmetro de vida útil

estrutura Container<'a> {
    dados: &'a str,
}

impl<'a> Contêiner<'a> {
fn new(texto: &'a str) -> Self {
        Contêiner {dados: texto}
    }

    fn get_data(&self) -> &str {
        auto.dados
    }
}

Armadilha 6: Invalidação do Iterador

//ERRO
fn principal() {
    deixe mut vec = vec![1, 2, 3, 4, 5];

    para eu em &vec {
        se *i % 2 == 0 {
            vec.push(*i * 2);  // ERRO: não é possível modificar durante a iteração
        }
    }
}

Solução: colete os índices primeiro

fn principal() {
    deixe mut vec = vec![1, 2, 3, 4, 5];

    deixe to_add: Vec<i32> = vec.iter()
        .filtro(|&&x| x % 2 == 0)
        .map(|&x| x * 2)
        .coletar();

    vec.extend(to_add);
    println!("{:?}",vec);
}

7. Padrões avançados: além da propriedade básica

Padrão 1: Mutabilidade Interior com RefCell

Às vezes você precisa alterar dados apenas com uma referência imutável:

usar std::cell::RefCell;

estrutura Registrador {
    logs: RefCell<Vec<String>>, // Pode sofrer mutação através de &self
}

impl Registrador {
    fn novo() -> Próprio {
        Registrador {
            registros: RefCell::new(Vec::new()),
        }
    }

    fn log(&self, message: &str) { // Leva &self, não &mut self
        self.logs.borrow_mut().push(message.to_string());
    }

    fn print_logs(&self) {
        para login self.logs.borrow().iter() {
            println!("{}", registro);
        }
    }
}

fn principal() {
    deixe logger = Logger::new();
    logger.log("Primeira mensagem");
    logger.log("Segunda mensagem");
    logger.print_logs();
}

Quando usar:

  • Implementação de caches ou loggers
  • Estruturas gráficas ou de árvore com mutabilidade interior
  • Simular objetos em testes

Cuidado: Verificação de empréstimo em tempo de execução – entra em pânico se as regras forem violadas!

deixe célula = RefCell::new(5);
deixe emprestado1 = cell.borrow();
deixe emprestado2 = cell.borrow_mut();  // PÂNICO: já peguei emprestado!

Padrão 2: Contagem de Referência com Rc

Compartilhe a propriedade dos dados com vários proprietários:

usar std::rc::Rc;

estrutura Nó {
    valor: i32,
    filhos: Vec<Rc<Nó>>,
}

fn principal() {
    deixe folha = Rc::new(Node {
        valor: 3,
        crianças: vec![],
    });

    deixe branch1 = Rc::new(Nó {
        valor: 1,
        filhos: vec![Rc::clone(&leaf)], // Propriedade compartilhada
    });

    deixe branch2 = Rc::new(Nó {
        valor: 2,
        filhos: vec![Rc::clone(&leaf)], // Ambos os ramos possuem folha
    });

    println!("Contagem de referências de folhas: {}", Rc::strong_count(&leaf));  //3
}

Pontos-chave:

  • Não é seguro para threads (use Arc para threads)
  • A contagem de referência tem sobrecarga
  • Cria ciclos se não for cuidadoso (use Weak para quebrar ciclos)

Padrão 3: Combinando Rc e RefCell

Vários proprietários com mutação:

usar std::rc::Rc;
usar std::cell::RefCell;

#[derivar(Depurar)]
estrutura SharedCounter {
    contagem: Rc<RefCell<i32>>,
}

impl SharedCounter {
    fn novo() -> Próprio {
        ContadorCompartilhado{
            contagem: Rc::new(RefCell::new(0)),
        }
    }

    fn incremento(&self) {
        *self.count.borrow_mut() += 1;
    }

    fn get(&self) -> i32 {
        *self.count.borrow()
    }
}

fn principal() {
    deixe contador1 = SharedCounter::new();
    deixe counter2 = SharedCounter {
        contagem: Rc::clone(&counter1.count),
    };

    contador1.incremento();
    contador2.increment();

    println!("Contagem: {}", contador1.get());  //2
}

Padrão 4: Padrão Construtor com Propriedade

estrutura Servidor {
    anfitrião: Corda,
    porta: u16,
    tempo limite: u64,
}

estrutura ServerBuilder {
    host: Opção<String>,
    porta: Opção<u16>,
    tempo limite: Opção<u64>,
}

impl ServerBuilder {
    fn novo() -> Próprio {
        Construtor de Servidores {
            anfitrião: Nenhum,
            porta: Nenhum,
            tempo limite: Nenhum,
        }
    }

    fn host(mut self, host: impl Into<String>) -> Self {
        self.host = Some(host.into());
        self // Move a propriedade de volta
    }

    porta fn(mut self, porta: u16) -> Self {
        self.port = Some(porta);
        eu mesmo
    }

    fn timeout(mut self, timeout: u64) -> Self {
        self.timeout = Alguns(tempo limite);
        eu mesmo
    }

    fn build(self) -> Resultado<Servidor, &'static str> {
        Ok(Servidor {
            host: self.host.ok_or("Host é necessário")?,
            porta: self.port.unwrap_or(8080),
            tempo limite: self.timeout.unwrap_or(30),
        })
    }
}

fn principal() {
    deixe servidor = ServerBuilder::new()
        .host("localhost")
        .porta(3000)
        .tempo limite(60)
        .construir()
        .desembrulhar();

    println!("Servidor: {}:{}", servidor.host, servidor.port);
}

Padrão 5: RAII (aquisição de recursos é inicialização)

A propriedade permite a limpeza automática de recursos:

usar std::fs::Arquivo;
use std::io::{self, Write};

estrutura LogFile {
    arquivo: Arquivo,
}

impl Arquivo de Log {
    fn new(caminho: &str) -> io::Result<Self> {
        Ok(ArquivoLog {
            arquivo: Arquivo::criar(caminho)?,
        })
    }

    fn write_log(&mut self, mensagem: &str) -> io::Result<()> {
        writeln!(self.file, "{}", mensagem)
    }
}

impl Soltar para LogFile {
    fn drop(&mut self) {
        println!("Fechando arquivo de log");
        //Arquivo fechado automaticamente quando descartado
    }
}

fn main() -> io::Result<()> {
    {
        deixe-o log = LogFile::new("app.log"?);
        log.write_log("Aplicativo iniciado"?);
        log.write_log("Processando dados"?);
    } // Arquivo fechado automaticamente aqui via Drop

    println!("Arquivo de log fechado automaticamente");
    Ok(())
}

8. Estratégias de refatoração do mundo real

Cenário 1: passando dados para funções

Antes (lutando contra o verificador de empréstimo):

estruturar usuário {
    nome: Corda,
    e-mail: String,
}

fn process_user(usuário: Usuário) {
    println!("Processando {}", usuário.nome);
}

fn principal() {
    deixe usuário = Usuário {
        nome: String::from("Alice"),
        email: String::from("[email protected]"),
    };

    usuário_processo(usuário);
    // println!("{}", usuário.nome);  //ERRO: usuário movido
}

Depois (pegar emprestado em vez de mover):

fn process_user(user: &User) { // Em vez disso, peça emprestado
    println!("Processando {}", usuário.nome);
}

fn principal() {
    deixe usuário = Usuário {
        nome: String::from("Alice"),
        email: String::from("[email protected]"),
    };

    usuário_processo(&usuário);
    println!("{}", usuário.nome);  // OK: o usuário ainda pertence
}

Cenário 2: Trabalhando com Coleções

Antes:

fn get_first_name(usuários: Vec<Usuário>) -> Option<String> {
    users.first().map(|u| u.name.clone()) // Clone desnecessário
}

fn principal() {
    deixe usuários = vec![/* ... */];
    deixe nome = get_first_name(usuários);
    // Não é mais possível usar usuários - movido
}

Depois:

fn get_first_name(usuários: &[Usuário]) -> Opção<&str> {
    usuários.first().map(|u| u.name.as_str())
}

fn principal() {
    deixe usuários = vec![/* ... */];
    deixe nome = get_first_name(&users);
    // usuários ainda utilizáveis
}

Cenário 3: estrutura com vários campos de string

Antes (muita clonagem):

fn build_full_name(primeiro: String, último: String) -> String {
    formato!("{} {}", primeiro, último)
}

fn principal() {
    deixe primeiro = String::from("John");
    deixe último = String::from("Doe");

    deixe completo = build_full_name(first.clone(), last.clone());
    println!("Primeiro: {}, Último: {}", primeiro, último);
}

Depois (use fatias de barbante):

fn build_full_name(primeiro: &str, último: &str) -> String {
    formato!("{} {}", primeiro, último)
}

fn principal() {
    deixe primeiro = String::from("John");
    deixe último = String::from("Doe");

    deixe completo = build_full_name(&primeiro, &último);
    println!("Primeiro: {}, Último: {}", primeiro, último);
}

Cenário 4: Resultados de cache

Problema: precisa de cache mutável com métodos imutáveis

Solução: Mutabilidade interior

usar std::cell::RefCell;
usar std::collections::HashMap;

struct ExpensiveCalculator {
    cache: RefCell<HashMap<i32, i32>>,
}

impl Calculadora Cara {
    fn novo() -> Próprio {
        Calculadora cara {
            cache: RefCell::new(HashMap::new()),
        }
    }

    fn calcular(&self, entrada: i32) -> i32 { // &self, não &mut self
        //Verifica cache
        se deixar Some(&cached) = self.cache.borrow().get(&input) {
            retornar em cache;
        }

        // Cálculo caro
        deixe resultado = entrada * entrada;

        // Armazena em cache
        self.cache.borrow_mut().insert(entrada, resultado);

        resultado
    }
}

fn principal() {
    deixe calc = ExpensiveCalculator::new();
    println!("{}", calc.calculate(5));  // Calculado
    println!("{}", calc.calculate(5));  // Do cache
}

Cenário 5: Estruturas de árvore

Desafio: relacionamentos entre pais e filhos criam conflitos de empréstimos

Solução: Use índices ou Rc/Weak

usar std::rc::{Rc, Fraco};
usar std::cell::RefCell;

estrutura Nó {
    valor: i32,
    pai: RefCell<Weak<Node>>,
    filhos: RefCell<Vec<Rc<Node>>>,
}

impl Nó {
    fn novo(valor: i32) -> Rc<Self> {
        Rc::new(Nó {
            valor,
            pai: RefCell::new(Fraco::new()),
            filhos: RefCell::new(vec![]),
        })
    }
fn add_child(pai: &Rc<Nó>, filho: Rc<Nó>) {
        *child.parent.borrow_mut() = Rc::downgrade(pai);
        parent.children.borrow_mut().push(child);
    }
}

fn principal() {
    deixe root = Node::new(1);
    deixe filho1 = Nó::novo(2);
    deixe filho2 = Nó::novo(3);

    Nó::add_child(&root, filho1);
    Nó::add_child(&root, filho2);

    println!("Root tem {} filhos", root.children.borrow().len());
}

9. Implicações de desempenho: abstrações de custo zero

Propriedade tem custo zero

O sistema de propriedade não tem sobrecarga de tempo de execução:

// Este código Rust:
processo fn(dados: Vec<i32>) -> i32 {
    dados.iter().sum()
}

// Compila no mesmo assembly que:
// processo int(int* dados, tamanho_t len) {
// int soma = 0;
// for (tamanho_t i = 0; i < len; i++) {
// soma += dados[i];
// }
// retorna soma;
// }

Prova: Verifique a montagem com cargo build --release e ferramentas como cargo-asm.

Quando a clonagem tem custo

// Caro: cópia profunda
deixe vec1 = vec![1, 2, 3, 4, 5];
deixe vec2 = vec1.clone();  // Aloca nova memória heap, copia todos os elementos

// Grátis: Referência
deixe vec1 = vec![1, 2, 3, 4, 5];
seja vec2 = &vec1;  // Sem alocação, apenas um ponteiro

Referência: Clonar vs Emprestar

use std::time::Instant;

fn process_by_value(dados: Vec<i32>) -> i32 {
    dados.iter().sum()
}

fn process_by_reference(dados: &[i32]) -> i32 {
    dados.iter().sum()
}

fn principal() {
    deixe os dados: Vec<i32> = (0..1_000_000).collect();

    //Clonar versão
    vamos começar = Instantâneo::agora();
    para _ em 0..1000 {
        deixe resultado = process_by_value(data.clone());  //Clone cada vez
    }
    println!("Clone: {:?}", start.elapsed());

    // Emprestar versão
    vamos começar = Instantâneo::agora();
    para _ em 0..1000 {
        deixe resultado = process_by_reference(&data);  //Sem clone
    }
    println!("Empréstimo: {:?}", start.elapsed());
}

// Resultados típicos:
//Clonar: 850ms
// Emprestar: 120ms

Ponteiro inteligente suspenso

// Rc tem pequena sobrecarga
usar std::rc::Rc;
use std::time::Instant;

fn with_rc(dados: Rc<Vec<i32>>) {
    deixe _ = dados.len();
}

fn with_ref(dados: &Vec<i32>) {
    deixe _ = dados.len();
}

// Rc tem 2 palavras (ponteiro + contagem de referências)
// A referência é uma palavra (apenas um ponteiro)
// Mas Rc permite propriedade compartilhada onde as referências não podem

Quando as despesas gerais são importantes:

  • Hot loops com milhões de iterações
  • Sistemas em tempo real
  • Sistemas embarcados com recursos limitados

Quando a sobrecarga não importa:

  • A maior parte do código do aplicativo
  • Quando permite um melhor design
  • Quando a clonagem seria mais cara

10. Guia de migração: de outros idiomas

Vindo de C++

Mentalidade C++:

std::string* createString() {
    return new std::string("olá");  //O chamador deve excluir
}

processo vazio() {
    std::string* s = createString();
    std::cout << *s;
    excluir;  //Limpeza manual
}

Equivalente à ferrugem:

fn criar_string() -> String {
    String::from("hello") // Propriedade transferida
}

processo fn() {
    vamos s = create_string();
    println!("{}",s);
} // Descartado automaticamente

Principais diferenças:

  • Sem manual novo/excluir
  • Sem ponteiros brutos em código seguro
  • As referências têm verificação vitalícia
  • Mover semântica por padrão

Vindo de Go

Vá mentalidade:

func processo(dados[]int) {
    data[0] = 100 // Muda o original
}

função principal() {
    números := []int{1, 2, 3}
    processo (números)
    fmt.Println(nums) // [100, 2, 3]
}

Rust requer mutabilidade explícita:

fn process(data: &mut [i32]) { // &mut explícito
    dados[0] = 100;
}

fn principal() {
    deixe mut nums = vec![1, 2, 3];  // palavra-chave mut necessária
    process(&mut nums);            // &mut explícito
    println!("{:?}", números);        // [100, 2, 3]
}

Principais diferenças:

  • A mutabilidade deve ser explícita
  • Sem corridas de dados ocultas
  • As referências são explícitas (& vs valor)

Vindo de Python

Mentalidade Python:

def modificar_lista(itens):
    items.append(4) # Muda o original

números = [1, 2, 3]
lista_modificar(números)
imprimir(num) # [1, 2, 3, 4]

Equivalente à ferrugem:

fn modificar_lista(itens: &mut Vec<i32>) {
    itens.push(4);
}

fn principal() {
    deixe mut nums = vec![1, 2, 3];
    modificar_list(&mut nums);
    println!("{:?}", números);  // [1, 2, 3, 4]
}

Principais diferenças:

  • Tudo em Python é referência; Rust distingue valores e referências
  • Python GC cuida da limpeza; Rust usa propriedade
  • Python permite mutação livremente; A ferrugem requer mut

Vindo de JavaScript

Mentalidade JavaScript:

function criarUsuário() {
    return { nome: "Alice", email: "[email protected]" };
}

deixe usuário = criarUsuário();
deixe usuário2 = usuário;  // Cópia superficial
user2.name = "Bob";
console.log(usuário.nome);  // "Bob" - ambos referem-se ao mesmo objeto

Comportamento de ferrugem:

#[derivar(Clone)]
estruturar usuário {
    nome: Corda,
    e-mail: String,
}

fn create_user() -> Usuário {
    Usuário {
        nome: String::from("Alice"),
        email: String::from("[email protected]"),
    }
}

fn principal() {
    deixe usuário = create_user();
    deixe usuário2 = usuário;  //Movido, não copiado
    // println!("{}", usuário.nome);  //ERRO: valor movido

    // Se você quiser copiar o comportamento:
    deixe usuário = create_user();
    deixe user2 = user.clone();  //clone explícito
    println!("{}", usuário.nome);  // OK
}

Principais diferenças:

  • Objetos JS são contados por referência; A ferrugem se move por padrão
  • JS tem GC; A ferrugem tem propriedade
  • A mutação JS é irrestrita; Rust impõe regras de empréstimo

Conclusão: adote o verificador de empréstimos

O sistema de propriedade parece restritivo no início, mas na verdade é libertador:

Sem vazamentos de memória: Se compilar, a memória será gerenciada corretamente ✅ Sem corridas de dados: Bugs simultâneos são detectados em tempo de compilação ✅ Sem uso após livre: Impossível acessar a memória liberada ✅ Desempenho previsível: sem pausas no GC, sem alocações ocultas ✅ Refatoração destemida: o compilador detecta alterações significativas

Obtenha serviços profissionais →

A curva de aprendizado

Semana 1-2: Frustração. O verificador de empréstimo rejeita tudo. Semana 3-4: Compreensão. Você começa a pensar em propriedade. Mês 2: Fluência. Você projeta APIs que funcionam com propriedade. Mês 3+: Maestria. Você escreve Rust mais rápido do que sua linguagem antiga.

Dicas Práticas para Aprendizagem

  1. Leia os erros do compilador com atenção - eles são excelentes professores
  2. Comece com programas pequenos – domine o básico antes de construir sistemas grandes
  3. Use clone() liberalmente no início - otimize mais tarde
  4. Não lute contra o verificador de empréstimo - redesenhe se estiver com dificuldades
  5. Estude o código padrão da biblioteca – veja como os especialistas fazem isso

Próximas etapas

  • Prática: Resolva problemas de Exercismo, LeetCode ou Advent of Code in Rust
  • Leia: The Rust Book, Rust by Example, Rustonomicon (ferrugem insegura)
  • Construir: Projetos reais forçam você a encontrar e resolver problemas reais
  • Contribuir: Projetos Rust de código aberto dão as boas-vindas aos recém-chegados

Recursos


Publicidade
Coffee cup

Gostou deste artigo?

Seu apoio me ajuda a criar mais conteúdo técnico aprofundado, ferramentas open-source e recursos gratuitos para a comunidade de desenvolvedores.

Tópicos Relacionados

Engr Mejba Ahmed

Sobre o Autor

Engr Mejba Ahmed

Engr. Mejba Ahmed builds AI-powered applications and secure cloud systems for businesses worldwide. With 10+ years shipping production software in Laravel, Python, and AWS, he's helped companies automate workflows, reduce infrastructure costs, and scale without security headaches. He writes about practical AI integration, cloud architecture, and developer productivity.

Discussion

Comments

0

No comments yet

Be the first to share your thoughts

Leave a Comment

Your email won't be published

9  -  5  =  ?

Comments

Leave a Comment

Comments are moderated before appearing.

Learning Resources

Expand Your Knowledge

Accelerate your growth with structured courses, verified certificates, interactive flashcards, and production-ready AI agent skills.

Sample Certificate of Completion

Sample certificate — complete any course to earn yours

Engr Mejba Ahmed

Engr Mejba Ahmed

Claude Code Expert · Online

👋

Hey there!

Quick Actions

WhatsApp Instant reply

Chat on WhatsApp

+880 1723 741224 · Instant reply

Popular Questions

Engr Mejba Ahmed is connected
Engr Mejba Ahmed is typing...
Engr Mejba Ahmed avatar

✉ Want me to follow up? Drop your email

Engr Mejba Ahmed avatar

📞 Connect Directly

Choose how you'd like to reach me

WhatsApp

+880 1723 741224

Email

[email protected]

✓ Details sent! I'll get back to you shortly.

Powered by OpenAI

335+

Blog Posts

25

AI Courses

63

Projects

Services & Expertise

Pricing & Process

Learning & Resources

Connect & Support