Photo by Enry B King on Unsplash
Aprendendo Rust: Ownership
Descubra o diferencial da linguagem mais amada dos últimos tempos
Este post é um resumo traduzido dos conceitos que você pode encontrar no livro, que é gratuito: The Rust Programming Language
Rust tem por filosofia não impactar negativamente a eficiência do seu programa, todo código escrito na linguagem tem a responsabilidade de ser eficiente, seguro e confiável, a principal razão que regulamenta isso é o conceito de ownership.
Rust não tem garbage collector, sendo assim, um dos pilares para programar na linguagem é entender profundamente sobre gerenciamento de memória, não irei me aprofundar neste tópico entretanto um conceito que você precisa obrigatoriamente entender é a diferença entre heap e stack.
Uma diferença importante à se saber é que alocar memória significa buscar um espaço na heap enquanto a stack funciona com valores não alocados sobre demanda.
Uma vez entendido os conceitos de memória, é importante entender as regras do ownership,
Todo valor precisa ter um dono.
Cada valor só pode, obrigatoriamente, ter um único dono por vez.
Quando o valor sair de seu contexto, ele será deletado da memória.
Para todo o dado que precisa estar na heap, ao invés de chamar explicitamente uma alocação de memória ou uma liberação de memória, Rust define essas 3 regras e trata em tempo de compilação se você está respeitando essas regras e se o seu código permite um gerenciamento de memória seguro, caso o contrário, seu código não irá compilar!
Lidando com strings
Diferente de tipos primitivos, que tem tamanho fixo de memória, strings podem ser muito problemáticas quando alocadas na stack em tempo de execução, trazendo insegurança para a memória do nosso programa, para lidar com isso o Rust tem duas etapas
Pedir somente a memória necessária ao alocador em tempo de execução.
Ter um meio de comunicar que nossa operação com essa string terminou e pode ser liberada.
A primeira etapa é resolvida com o método from dentro do módulo de String, e esse método utiliza da regra 1 de ownership para determinar um dono sobre aquela alocação de memória.
let text = String::from("Olá mundo");
// String livre para qualquer operação
A segunda etapa consiste no funcionamento da regra 3 de ownership, ao repassar o meu valor alocado a um novo escopo ou caso o meu escopo termine, o Rust invoca uma função chamada drop e limpa esse valor da memória.
Um problema muito comum, resolvido por ownership se tratando de string, é o uso de shallow copy ou deep copy na passagem de valores de uma variável para outra, mas em Rust não funciona assim.
Rust opta por não fazer nenhum tipo de cópia, aplicando somente a regra 2 do ownership, caso o valor tenha um novo dono, ambos os donos apontarão para o mesmo lugar e o primeiro deixará de ser válido, já que não queremos ter problemas como limpar o mesmo dado duas vezes da memória. Para ser possível manter os dois dados, precisamos deliberamente invocar o método clone da string, gerando um novo espaço de memória.
let text = String::from("Olá mundo");
let text_2 = text.clone();
println!("{}, {}", text, text_2);
Lidando com primitivos
Quando um valor inteiro como 5
quando é diretamente atribuído, a outra variável, ele é copiado pois esse inteiro tem o tipo u32
, e o compilador sabe exatamente quando de memória ele vai precisar e utilizará, sendo assim é muito fácil de lidar com essa cópia sem impactar sua memória e sua velocidade.
let x = 5; // É possível
let y = "Caranguejo" // Não é possível
Todos os tipos abaixo fazem cópia automática sem custo:
Integer
Bool
Floating point
Char
Tuples
Existem ainda mais maneiras de lidar com ownership, porém vamos dividir em etapas.
Vejo você no próximo post 😁