Aprendendo Rust: Borrowing

Aprenda a lidar com ciclo de vida das alocações em memória

Complementando nosso conhecimento sobre gerenciamento de memória com ownership em Rust, sabe-se que caso uma variável seja declarada na heap ela deixará de existir caso saia de seu contexto, como faremos para passar adiante um valor sem deixar esse valor ser limpo da memória?

Este post é um resumo traduzido dos conceitos que você pode encontrar no livro, que é gratuito: The Rust Programming Language

Borrowing, quer dizer emprestimo em inglês e funciona exatamente assim, imagine se você precisa emprestar seu carro a um amigo, você não iria copiar seu próprio carro para cede-lo, bem menos transferir o seu bem para o nome do seu amigo, você emprestaria a ele e assim funcionam as referências em Rust. Uma referencia é a informação de qual ponto na memória está presente aquele valor, por padrão, imutável e sem passar ownership a quem recebe esse valor.

Diferente de simples ponteiros, referências garantem sempre apontar para um endereço válido na memória. No exemplo abaixo, você pode ver como nós recebemos uma estrutura retangle, passamos essa estrutura a uma função area sem tomar o ownership pois os valores da estrutura retangle ainda serão usados nesse contexto.

pub fn calculate(width: i32, heigth: i32) {
    let retangle = Retangle { width, heigth };

    let retangle_area = area(&retangle);
    // retangle é passado a fn area, com o caracter & 
    // e este representa que a referência de retangle
    // foi borrowed(emprestada).

    println!(
        "Retangle area with {} width and {} heigh is: {}",
        retangle.width,
        retangle.heigth,
        retangle_area
    )
}

fn area(retangle: &Retangle) -> i32 {
    // area recebe somente a referencia do valor emprestado
    // e devolve um valor a partir disso.
    retangle.width * retangle.heigth
}

Referências mutáveis

Em alguns casos, precisamos interromper a imutabilidade das referências para modificar um valor, nesse caso é possível, basta adicionar a keyword mut na montagem de variável e também nas passagens de parâmetro. Entretanto referências mutáveis tem uma grande restrição, só é possível ter uma única referência mutável para o mesmo valor por vez, isso acontece para previnir um conceito chamado data racing.

 let mut number = 5;

 let width = &mut number;
 let heigth = &mut number;

 println!("{}, {}", width, heigth);

// Esse código não compila

Data Racing

Semelhante a race condiction e pode acontecer por uma combinação de 3 fatores

  • Dois ou mais ponteiros acessam o mesmo dado ao mesmo tempo

  • Pelo menos um desses ponteiros é usado para modificar o dado

  • Não existe lógica para sincronizar o acesso ao dado

A prevenção desse acontecimento, gerando inclusive erro de compilação quando ocorre, existe para não gerar dados indefinidos em tempo de execução, dificultando observar os problemas e resolve-los depois, para contornar esse problema basta adicionar um novo escopo com shadowing, assim você garante que não vai estar rodando o código ao mesmo tempo.

 let mut number = 5;
 let width = &mut number;

 {
    // novo escopo com shadowing
     let heigth = &mut number;
     println!("{}", heigth);
 }

 println!("{}", width);

// Esse código compila

Uma regra parecida com o data racing, é a não permissão de um mesmo dado ser referênciado como mutável e imutável ao mesmo tempo, a não ser que, a referência mutável aconteça depois que todas as referências imutáveis não tenham mais uso, isso acontece porque suas referências imutáveis não podem ter seu dado modificado em tempo de execução através desta mutável.

 let mut number = 5;

 let width = &number;
 let heigth = &mut number;

 println!("{}, {}", width, heigth);

// Esse código não compila porque o Rust 
// não permite o mutável afetar o imutável

Dangling References

É normal em linguagens com ponteiros é fácil de criar um “ponteiro quebrado” onde a referência na memória já foi passado a outro valor, em Rust o compilador garante que uma referência nunca será quebrada, no sentido de alguém usar um valor de um dado x em um momento onde ele já foi liberado da memória, como o caso do ownership fugindo ao escopo.

Reforçando o fato de que referências em Rust precisam sempre estarem válidas no seu escopo, atendendo as regras explicadas acima.

Por hoje é isso pessoal, vejo vocês no próximo post 😁