Ownership
Ownership é o método que Rust usa para o seu gerenciamento de memória, basicamente enquanto algum escopo do código tem aquele pedaço de memória ele é da nossa aplicação, quando esse pedaço de memória sai deste escopo ela é devolvida para o Sistema Operacional.
Vamos entender um pouco melhor como funciona o escopo de uma variável.
fn main() { let a = 10; if a == 10 { let b = 1; /* Neste ponto do código as variáveis 'a' e 'b' existem */ } /* a partir deste ponto não é possível acessar a variável 'b', pois ela esta fora de escopo */ println!("Valor de b {}", b); }
Ao tentar compilar o código acima temos o seguinte erro:
error[E0425]: cannot find value `b` in this scope
--> main.rs:14:31
|
14 | println!("Valor de b {}", b);
| ^ help: a local variable with a similar name exists: `a`
error: aborting due to previous error
Neste caso 'b' pertence a um escopo menor do que 'a', a partir do momento que sai do bloco if
a variável 'b' desaparece.
E como isso funciona no sistema de ownership? Para explicar isso iremos utilizar o tipo que aprendemos no começo deste módulo, as Strings.
fn main() { let mut meu_texto = String::from("Rust4Noobs"); printa_string(meu_texto); /* Vamos tentar fazer outra coisa com essa String */ meu_texto.push_str(", é legal!"); } fn printa_string(string: String) { println!("{}", string); }
Espera... Não compila, temos o seguinte erro:
error[E0382]: borrow of moved value: `meu_texto`
--> main.rs:7:5
|
2 | let mut meu_texto = String::from("Rust4Noobs");
| ------------- move occurs because `meu_texto` has type `String`, which does not implement the `Copy` trait
3 | printa_string(meu_texto);
| --------- value moved here
...
7 | meu_texto.push_str(", é legal!");
| ^^^^^^^^^ value borrowed here after move
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
Vamos entender o que aconteceu aqui.
O tipo String
é um "tipo especial", ele sempre é passado por referência, nunca é feito uma cópia de seu valor, quando chamamos a função printa_string
e em seu parâmetro passamos nossa String, a posse de sua memória é transferida para a função, quando a função termina a memória é devolvida para o Sistema Operacional.
fn main() { let mut meu_texto = String::from("Rust4Noobs"); printa_string(meu_texto); /* Vamos tentar fazer outra coisa com essa String */ meu_texto.push_str(", é legal!"); } fn printa_string(string: String) { println!("{}", string); } //a partir daqui a memória foi devolvida para o sistema
Mas se ainda quisermos utilizar a variável meu_texto
? Podemos fazer a função printa_string
retornar a String passada por argumento e pegar o ownership de volta.
fn main() { let mut meu_texto = String::from("Rust4Noobs"); meu_texto = printa_string(meu_texto); meu_texto.push_str(", é legal!"); println!("{}", meu_texto); } fn printa_string(string: String) -> String { println!("{}", string); string }
Agora o código ira compilar, todavia esta não é a única maneira de fazer isso, lembra dos ponteiros? Vamos utilizar aqui.
fn main() { let mut meu_texto = String::from("Rust4Noobs"); printa_string(&meu_texto); meu_texto.push_str(", é legal!"); println!("{}", meu_texto); } fn printa_string(string: &String) { println!("{}", string); }
E também funcionou, por enquanto vamos tentar pensar da seguinte maneira, como eu utilizei o ponteiro, eu emprestei a esta função a variável, ela fez o que tinha que fazer e me devolveu. Não perdemos o ownership da variável.
Utilizando esta estratégia de passagem por referência conseguimos utilizar algumas outras coisas como as referências mutáveis, modificar o valor sem perder o ownership
fn main() { let mut meu_texto = String::from("Rust4Noobs"); printa_string(&meu_texto); adiciona_texto(&mut meu_texto); println!("{}", meu_texto); } fn printa_string(string: &String) { println!("{}", string); } fn adiciona_texto(string: &mut String) { string.push_str(", é legal!"); }
E temos sucesso novamente.