DEV Community

Marlon Jerold
Marlon Jerold

Posted on

Ownership & Borrowing

Nesse blog estarei informando o que o Rust se difere de outras linguagens, de alto nível até as de baixo nível que temos no mercado.

Mas antes, é interessante que você já tenha uma noção sobre a base dela, como seus tipos e etc.
Vamos iniciar!

Você já deve saber que um dos principais desafios que temos em programação, é o manuseio da memória Heap, a famosa gestão.
Aqui que entramos no assunto de nosso blog, os tais dos Ownership & Borrowing vem para burlar essa realidade.

Quando compilamos com o Rust, o Borrow Checker visualiza se o que estamos fazendo está correto quando se trata de gerenciamento de memória, “Nem compila”.

Em programação temos essas séries de problemas que o Borrow Checker mete as caras.

  • Null Pointer Exception
  • Seggmentation Fault
  • Memory Leak
  • Danpling Pointer
  • Double free
  • Use aflter free
  • Data Races

Falando um pouco de Borrow Checker, ele tem um comportamento interessante quando estamos lidando com esses erros, ele pega apenas a função, e o que significa?

Significa que o Borrow Checker é acoplado ao nível função, focando na função que está sendo trabalhada, facilitando nossa vida.

Em Rust, é interessante entender o sistema de tipos, por exemplo, os tipos Copy, tipos que podem ser copiados automaticamente. Por exemplo.

fn main() {
    let mut a: i32 = 1; // i32 é um tipo Copy
    let b: = a;
    println!("Olá, o valor de A inicial = {}", a);
    println!("Olá, o valor de B inicial = {}", b);
    a = 21;
    println!("Olá, o valor de A final = {}", a);
    println!("Olá, o valor de B final = {}", b);
}
Enter fullscreen mode Exit fullscreen mode

Os tipos Copy faz possível isso acontecer, o Rust gera uma cópia, diferentemente de outras linguagens que faz uma referência, ao utilizar esse Copy é possível esse comportamento. O Rust cria um novo valor 1, esse valor agora é de b, diferentemente de uma instância nova. Pensando nessa independência, ao rodar o código acima, você terá esse resultado.

Olá, o valor de A inicial = 1
Olá, o valor de B inicial = 1
Olá, o valor de A final = 21
Olá, o valor de B final = 1
Enter fullscreen mode Exit fullscreen mode

Agora pense que você não deseja criar uma cópia, você ao adotar o valor de a para b, você criará uma referência manualmente.

fn main() {
    let a: i32 = 1; // i32 é um tipo Copy
    let b = &a;
    println!("Olá, o valor de A inicial = {}", a);
    println!("Olá, o valor de B inicial = {}", b);
}

Enter fullscreen mode Exit fullscreen mode

Ao rodar esse pequeno código, você verá que foi gerado uma referência de ‘b’ para ‘a’, agora, caso eu tenha que alterar o valor de ‘a’, será alterado o valor de ‘b’.

Mas antes, precisamos deixar a variável a, mutável. Como estamos querendo alterar o valor de uma variável, temos um tipo de dados que permite a mutabilidade, como o ‘Refcell’ por exemplo, embora também tenha o ‘&mut’ criando assim uma referência mutável.

fn main() {
    let a = RefCell::new(1);// i32 é um tipo Copy
    let b = &a;
    println!("Olá, o valor de A inicial = {}", a.borrow());
    println!("Olá, o valor de B inicial = {}", b.borrow());

    *a.borrow_mut() = 11;
    println!("Olá, o valor de A inicial = {}", a.borrow());
    println!("Olá, o valor de B inicial = {}", b.borrow());

}
Enter fullscreen mode Exit fullscreen mode

Está sendo permitido que o valor de ‘a’ seja alterado. Já no método ‘borrow()’, foi utilizado para obter uma referência imutável ao valor. Já o método ‘borrow_mut()’ é utilizado para obter uma referência mutável.

Nesse sentido, a saída será:

Olá, o valor de A inicial = 1
Olá, o valor de B inicial = 1
Olá, o valor de A inicial = 11
Olá, o valor de B inicial = 11
Enter fullscreen mode Exit fullscreen mode

Essa foi uma das formas que existe para realizar essa operação.
Vamos agora entender como usar a memória Heap!

Aqui entramos no conceito, ao utilizar o String, estamos alocando na memória Heap automaticamente, e o passa a ter posse de ‘a’, algo como ‘Dono’. A variável ‘a’ é dona de ‘Pato’.

fn main() {
    let a = String::from("Pato"); // String está alocada na Heap    
}
Enter fullscreen mode Exit fullscreen mode

o que seria o Borrow of Moved Value?

Você está utilziando o String, o valor está alocado na Heap, e ele por padrão não pode ser copiado, porém queremos que o ‘b’ tenha o valor de ‘a’. Nesse cenários acontece o “Move”, o valor de ‘a’ é movido para o ‘b’, ao rodar essa pequeno código você irá notar que terá um erro.

fn main() {
    let a = String::from("Pato"); // String está alocada na Heap    
    let b = a; // 'Move' movendo a para b
    println!("O valor de A é {}", a);
    println!("O valor de B é {}", b);
}
Enter fullscreen mode Exit fullscreen mode
O erro:

   Compiling array_element_acess v0.1.0 (/home/marlon/projects/array_element_acess)
error[E0382]: borrow of moved value: `a`
 --> src/main.rs:4:35
  |
2 |     let a = String::from("Pato"); // String está alocada na Heap    
  |         - move occurs because `a` has type `String`, which does not implement the `Copy` trait
3 |     let b = a; // movendo a para b
  |             - value moved here
4 |     println!("O valor de A é {}", a);
  |                                   ^ value borrowed here after move
Enter fullscreen mode Exit fullscreen mode

Percebe que agora estamos querendo acessar um valor que já foi movido para o ‘b’, quando temos linguagens que utilizando de um coletor, esses erros não acontece, porém aqui, quando utilizamos o Move o Rust não permite.

Agora, para resolver esse problema, podemos fazer o “Emprestimo”, que seria passar o ‘&’ em onde desejamos, o valor agora não é mais movido, é ‘Emprestado’.

fn main() {
    let a = String::from("Pato"); // String está alocada na Heap    
    let b = &a; //Empreste para b
    println!("O valor de A é {}", a);
    println!("O valor de B é {}", b);
}
Enter fullscreen mode Exit fullscreen mode

Ao rodar esse código, você irá receber esse resultado

O valor de A é Pato
O valor de B é Pato
Enter fullscreen mode Exit fullscreen mode

Quais regras do Onwership em Rust?

  1. Cada valor tem um dono (owner)
  2. Só pode ter um único dono
  3. Quando o dono sai de escopo o valor é limpo
  4. A posse (Ownership) pode ser movida a outro dono

Funções

Ao utilizar o &str, estamos utilizando um tipo Copy e como já vimos o que ocorre é que é gerado uma cópia.

fn say_hello(text: &str) {
    println!("Hello, {text}");
}
fn say_goodbye(text: &str){
    println!("Goodbye, {text}");
}
fn main() {
    let name = "Pato"; // static, str é do tipo Copy
    say_hello(name);
    say_goodbye(name);
}
Enter fullscreen mode Exit fullscreen mode

Ao rodar esse código, será retornado essa resposta:

Hello, Pato
Goodbye, Pato
Enter fullscreen mode Exit fullscreen mode

Agora, o que podemos fazer agora para utilizar a memória Heap?
Podemos já tirar o ‘&str’ para String.

Para realizar uma cópia na memória Heap, você precisará utilizar o ‘clone()’ ele tem custos, então tomar cuidado no que você está fazendo na Heap.

fn say_hello(text: String) {
    println!("Hello, {text}");
}
fn say_goodbye(text: String){
    println!("Goodbye, {text}");
}
fn main() {
    let name = "Pato".to_string(); 
    say_hello(name.clone()); // Clone.
    say_goodbye(name); 
}
Enter fullscreen mode Exit fullscreen mode

Agora, uma forma de resolver melhor esse problema, seria a utilização de empréstimos, como já vimos anteriormente

fn say_hello(text: &String) {
    println!("Hello, {text}");
}
fn say_goodbye(text: &String){
    println!("Goodbye, {text}");
}
fn main() {
    let name = "Pato".to_string(); 
    say_hello(&name); // Borrow, Emprestar
    say_goodbye(&name); 
}
Enter fullscreen mode Exit fullscreen mode

Só temos um único dono!

Regras de Borrowing

  1. Podemos ter uma única referência caso ela seja mutável
  2. Podemos ter várias quando são todas imutáveis

Nesse novo código, estamos falando de empréstimos!

`Só temos um único dono!
Regras de Borrowing

  1. Podemos ter uma única referência caso ela seja mutável
  2. Podemos ter várias quando são todas imutáveis

Nesse novo código, estamos falando de empréstimos!`

Espero que tenha curtido um pouco sobre Ownership & Borrowing, entender o que são os imutáveis e mutáveis, empréstimos, donos, são alguns conceitos que são importantes, entender o que é Move e Empréstimos vai ser crucial no desenvolvimento Rust.

Top comments (0)