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.
Top comments (0)