Semântica de Movimento
Uma atribuição transferirá o ownership entre variáveis:
fn main() { let s1: String = String::from("Olá!"); let s2: String = s1; println!("s2: {s2}"); // println!("s1: {s1}"); }
- A atribuição de
s1
as2
transfere o ownership. - Quando
s1
sai do escopo, nada acontece: ele não tem ownership. - Quando
s2
sai do escopo, os dados da string são liberados.
Antes de mover para s2
:
Depois de mover para s2
:
Quando você passa um valor para uma função, o valor é atribuído ao parâmetro da função. Isso transfere a ownership:
fn say_hello(name: String) { println!("Olá {name}") } fn main() { let name = String::from("Alice"); say_hello(name); // say_hello(name); }
-
Mencione que isso é o oposto dos defaults (padrões) em C++, que copia por valor, a menos que você use
std::move
(e seu construtor esteja definido!). -
Apenas o ownership é movido. A geração de código de máquina para manipular os dados é uma questão de otimização, e essas cópias são agressivamente otimizadas.
-
Valores simples (tais como inteiros) podem ser marcados como
Copy
(cópia) (veja slides mais adiante). -
No Rust, clones são explícitos (utilizando-se
clone
).
No exemplo say_hello
:
- Com a primeira chamada para
diga_ola
,main
desiste da ownership denome
. Depois disso,nome
não pode mais ser usado dentro demain
. - A memória do heap alocada para
name
será liberada no final da funçãosay_hello
. main
pode manter a ownership se passarnome
como uma referência (&name
) e sesay_hello
aceitar uma referência como um parâmetro.- Alternativamente,
main
pode passar um clone denome
na primeira chamada (name.clone()
). - Rust torna mais difícil a criação de cópias inadvertidamente do que o C++, tornando padrão a semântica de movimento e forçando os programadores a tornar os clones explícitos.
Mais para Explorar
Cópias Defensivas em C++ Moderno
O C++ moderno resolve isso de maneira diferente:
std::string s1 = "Cpp";
std::string s2 = s1; // Duplica os dados em s1.
- Os dados de
s1
no heap são duplicados es2
obtém sua própria cópia independente. - Quando
s1
es2
saem de escopo, cada um libera sua própria memória.
Antes da atribuição por cópia:
Após atribuição por cópia:
Pontos chave:
-
O C++ fez uma escolha ligeiramente diferente do Rust. Como
=
copia dados, os dados da string devem ser clonados. Caso contrário, obteríamos uma dupla liberação quando qualquer string saísse de escopo. -
O C++ também possui
std::move
, que é usado para indicar quando um valor pode ser movido. Se o exemplo fosses2 = std::move(s1)
, nenhuma alocação de heap seria feita. Após a movimentação,s1
estaria em um estado válido, mas não especificado. Diferentemente do Rust, o programador pode continuar usandos1
. -
Diferentemente do Rust,
=
em C++ pode executar código arbitrário conforme determinado pelo tipo que está sendo copiado ou movido.