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 a s2 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:

StackHeaps1ptrRustlen4capacity4

Depois de mover para s2:

PilhaHeaps1ponteiroRusttamanho4capacidade4s2ponteirotamanho4capacidade4(inacessĂ­vel)

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);
}
This slide should take about 5 minutes.
  • 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 de nome. Depois disso, nome nĂŁo pode mais ser usado dentro de main.
  • A memĂłria do heap alocada para name serĂĄ liberada no final da função say_hello.
  • main pode manter a ownership se passar nome como uma referĂȘncia (&name) e se say_hello aceitar uma referĂȘncia como um parĂąmetro.
  • Alternativamente, main pode passar um clone de nome 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 e s2 obtĂ©m sua prĂłpria cĂłpia independente.
  • Quando s1 e s2 saem de escopo, cada um libera sua prĂłpria memĂłria.

Antes da atribuição por cópia:

StackHeaps1ptrCpplen3capacity3

Após atribuição por cópia:

StackHeaps1ptrCpplen3capacity3s2ptrCpplen3capacity3

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 fosse s2 = 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 usando s1.

  • Diferentemente do Rust, = em C++ pode executar cĂłdigo arbitrĂĄrio conforme determinado pelo tipo que estĂĄ sendo copiado ou movido.