Ownership & Borrowing
Marlon Jerold
Posted on July 18, 2024
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);
}
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
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);
}
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());
}
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
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
}
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);
}
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
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);
}
Ao rodar esse código, você irá receber esse resultado
O valor de A é Pato
O valor de B é Pato
Quais regras do Onwership em Rust?
- Cada valor tem um dono (owner)
- Só pode ter um único dono
- Quando o dono sai de escopo o valor é limpo
- 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);
}
Ao rodar esse código, será retornado essa resposta:
Hello, Pato
Goodbye, Pato
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);
}
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);
}
Só temos um único dono!
Regras de Borrowing
- Podemos ter uma única referência caso ela seja mutável
- 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
- Podemos ter uma única referência caso ela seja mutável
- 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.
Posted on July 18, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 29, 2024