WEB3DEV

Cover image for Domínio de Propriedade em Rust: 10 Coisas Essenciais Que Você Precisa Saber
Paulo Gio
Paulo Gio

Posted on

Domínio de Propriedade em Rust: 10 Coisas Essenciais Que Você Precisa Saber

Domine o sistema de propriedade de Rust para escrever código livre de bugs que é executado de maneira extremamente rápida. Este guia explica as 10 regras de propriedade de Rust com exemplos reais de código.

Introdução

Linguagens de programação como C/C++ dão aos desenvolvedores muito controle sobre a gestão de memória. No entanto, isso vem com um grande risco — é fácil cometer erros que levam a falhas, vulnerabilidades de segurança ou bugs!

Alguns problemas comuns que programadores de C/C++ enfrentam incluem:

  • Usar memória após tê-la liberado. Isso leva a falhas ou bugs estranhos mais adiante.
  • Esquecer de liberar a memória quando terminar de usá-la. Isso causa vazamentos de memória ao longo do tempo.
  • Duas partes do código acessando a mesma memória ao mesmo tempo. Isso pode causar condições de corrida.

Para evitar esses problemas, o Rust usa um sistema de propriedade. Isso adiciona algumas regras que o compilador verifica para garantir a segurança da memória.

A ideia principal é que todo valor em Rust deve ter um proprietário. O proprietário é responsável por esse valor — gerenciando seu ciclo de vida, liberando-o, permitindo acesso a ele, etc.

Ao rastrear a propriedade, o compilador do Rust pode garantir que os valores sejam válidos quando você os usa, evitar condições de corrida de dados e liberar memória quando necessário. Tudo isso sem a necessidade de um coletor de lixo!

Este modelo de propriedade potencializa a segurança e a velocidade do Rust. Seguindo algumas regras de propriedade, seus programas Rust serão protegidos de classes inteiras de problemas relacionados à memória.

Vamos percorrer os 10 superpoderes de propriedade que o Rust fornece:

Cada valor tem uma variável proprietária

No Rust, cada valor, como uma string ou um inteiro, tem um proprietário. O proprietário é a variável que está vinculada a esse valor. Por exemplo:

let x = 5;
Enter fullscreen mode Exit fullscreen mode

Aqui, x é o proprietário do valor inteiro 5. A variável x monitora e gerencia esse valor 5.

Pense nisso como x assumindo a propriedade e responsabilidade sobre esse valor 5. x é agora o chefe desse valor!

Esse sistema de propriedade evita situações confusas com múltiplas variáveis apontando para o mesmo valor. Com propriedade única, fica claro que x é o único proprietário dos dados 5.

Quando o proprietário sai do escopo, o valor é descartado

Quando a variável proprietária sai do escopo, o Rust chamará a função drop para o valor e o limpará:

{
  let y = 5; // y é o proprietário de 5
} // y sai de escopo e 5 é descartado
Enter fullscreen mode Exit fullscreen mode

O escopo refere-se ao bloco em que uma variável é válida. No exemplo acima, y existe apenas dentro das chaves {}. Uma vez que a execução sai desse bloco, y desaparece e o valor 5 é descartado.

Essa liberação automática de dados evita vazamentos de memória. Assim que o proprietário y desaparece, o Rust limpa o valor. Chega de preocupações com ponteiros pendentes ou sobrecarga de memória!

Só pode haver um proprietário de cada vez

O Rust impõe propriedade única para cada valor. Isso evita esquemas caros de contagem de referências:

let z = 5; // z possui 5
let x = z; // a propriedade de z é transferida para x 
// z não possui mais 5!
Enter fullscreen mode Exit fullscreen mode

Neste exemplo, transferir a propriedade de z para x é barato. Algumas linguagens usam contagem de referência onde várias variáveis podem apontar para um valor, mas isso leva a uma sobrecarga.

Com propriedade única, o Rust apenas atualiza uma variável proprietária interna para mover a propriedade de z para x. Sem atualizações caras de contadores.

Quando o proprietário é copiado, os dados são movidos

Atribuir uma variável proprietária a uma nova variável move os dados:

let s1 = "hello".to_string(); // s1 possui "hello"
let s2 = s1; // a propriedade de s1 é transferida para s2   
             // s1 não pode mais usar "hello"
Enter fullscreen mode Exit fullscreen mode

Aqui criamos a string “hello” e a vinculamos a s1. Depois, atribuímos s1 a uma nova variável s2.

Isso transfere a propriedade de s1 para s2. s1 não é mais o proprietário da string! Os próprios dados não foram copiados, apenas a propriedade foi movida.

Isso evita cópias acidentais caras. Para realmente copiar os dados, você deve usar o método clone() do Rust para deixar a intenção clara.

A propriedade pode ser emprestada por meio de referências

Podemos criar variáveis de referência que emprestam a propriedade:

let s = "hello".to_string(); // s possui "hello"  
let r = &s; // r empresta imutavelmente de s
            // s ainda possui "hello"
println!("{}", r); // imprime "hello"
Enter fullscreen mode Exit fullscreen mode

O operador & cria uma referência r que pega emprestado a propriedade de s para esse escopo.

Pense em r como temporariamente pegando emprestado os dados que s possui. s ainda retém a propriedade total sobre os dados. r apenas tem permissão para ler a string "hello".

Referências mutáveis têm acesso exclusivo

Só pode haver uma referência mutável para dados de cada vez:

let mut s = "hello".to_string();  

let r1 = &mut s; // r1 empresta s mutavelmente

let r2 = &mut s; // erro!
Enter fullscreen mode Exit fullscreen mode

Isso evita condições de corrida em tempo de compilação. A referência mutável r1 tem acesso exclusivo de escrita a s, então nenhuma outra referência é permitida até que r1 termine.

Isso o salva de bugs de concorrência sutis, tornando o acesso simultâneo aos dados impossível.

Referências devem durar menos que seus proprietários

As referências devem ter tempos de vida mais curtos do que aquilo que estão emprestando:

{
  let r;               
  let s = "hello".to_string();

  r = &s; // erro! r não vive o suficiente   

} // s é descartado aqui
Enter fullscreen mode Exit fullscreen mode

Aqui r sai de escopo antes de s. Então r estaria referenciando dados de s depois que s fosse descartado!

Rust evita bugs de uso após liberação, impondo esta regra de que as referências não podem viver mais que seus proprietários.

Structs podem ser passadas por movimento ou empréstimo

Podemos transferir ou emprestar a propriedade de dados de uma struct:

struct User {
  name: String,
  age: u32  
}

let user1 = User {
  name: "John".to_string(),
  age: 27
}; // user1 é o proprietário da struct   

let user2 = user1; // propriedade transferida para user2
                   // user1 não pode mais usar isso

let borrow = &user1; // empresta a struct por referência  
                     // user1 ainda é o proprietário dos dados
Enter fullscreen mode Exit fullscreen mode

Structs agrupam dados relacionados, mas as regras de propriedade ainda se aplicam aos seus campos.

Podemos passar a propriedade de uma struct para funções e threads, ou emprestá-las de forma imutável. As mesmas regras de único proprietário/empréstimo tornam o uso de structs seguro.

A propriedade funciona da mesma forma no heap

A propriedade se aplica a dados alocados no heap:

let s1 = String::from("hello"); // s1 na pilha possui dados do heap 

let s2 = s1.clone(); // dados do heap copiados para novo local   
                     // s1 e s2 possuem dados separados

let r = &s1; // r empresta imutavelmente os dados do heap de s1   
             // s1 ainda é o proprietário dos dados do heap
Enter fullscreen mode Exit fullscreen mode

Aqui, s1 é alocado na pilha, mas contém uma String que aponta para o texto alocado no heap. As mesmas regras de propriedade se aplicam, mesmo que esteja no heap.

O Rust previne liberações duplicadas ou uso após liberação, mesmo quando trabalha com ponteiros. O sistema de propriedade mantém as alocações no heap seguras.

Propriedade possibilita concorrência segura

A propriedade potencializa a concorrência destemida do Rust:

`use std::thread;

let v = vec![1, 2, 3]; 

let handle = thread::spawn(move || {
  println!("Here's a vector: {:?}", v);
});

handle.join().unwrap();
Enter fullscreen mode Exit fullscreen mode

Nós transferimos a propriedade de v para a thread criada usando um fechamento move. Isso impede o acesso concorrente a v a partir de várias threads.

O sistema de propriedade torna a concorrência segura e fácil em Rust. Não há necessidade de bloqueio porque o compilador impõe propriedade única.

Conclusão

O sistema de propriedade do Rust é projetado para manter seu código seguro e rápido. Ao impor algumas regras-chave, classes inteiras de bugs de memória são eliminadas!

Algumas das principais lições sobre propriedade:

  • Cada valor do Rust tem uma variável proprietária responsável por aquele valor.
  • Quando o proprietário desaparece, o valor é limpo automaticamente. Acabaram-se os vazamentos!
  • Os valores só podem ter um proprietário de cada vez. Isso evita confusões.
  • Referências podem emprestar propriedade temporariamente de forma segura.
  • O compilador verifica se as referências são válidas para prevenir ponteiros pendentes.
  • Regras de propriedade evitam condições de corrida e possibilitam concorrência fácil.

Então, enquanto a propriedade te força a pensar sobre gerenciamento de memória, é por um bom motivo — elimina montes de bugs potenciais!

O sistema de propriedade é uma grande parte do que torna o Rust tão confiável e rápido. Seguir as regras de propriedade do Rust manterá seu código seguro, mesmo quando seus programas se tornarem grandes.

Então, adote as verificações em tempo de compilação do Rust como orientações úteis, e não como restrições. Seu futuro eu agradecerá quando seu programa Rust rodar suavemente, sem travamentos ou falhas de segurança!

Se você gostou do meu post, siga-me em JSDevJournal.

Artigo original publicado por JSDevJournal. Traduzido por Paulinho Giovannini.

Oldest comments (0)