Semántica de movimiento

Una asignación transferirá su ownership entre variables:

fn main() {
    let s1: String = String::from("Hello!");
    let s2: String = s1;
    println!("s2: {s2}");
    // println!("s1: {s1}");
}
  • La asignación de s1 a s2 transfiere el ownership.
  • Cuando s1 queda fuera del ámbito, no ocurre nada: no le pertenece nada.
  • Cuando s2 sale del ámbito, los datos de la cadena se liberan.

Antes de mover a s2:

StackHeaps1ptrRustlen4capacity4

Después de mover a s2:

StackHeaps1ptrRustlen4capacity4s2ptrlen4capacity4(inaccessible)

Cuando pasas un valor a una función, el valor se asigna al parámetro de la función. Esta acción transfiere el ownership:

fn say_hello(name: String) {
    println!("Hello {name}")
}

fn main() {
    let name = String::from("Alice");
    say_hello(name);
    // say_hello(name);
}
This slide should take about 10 minutes.
  • Menciona que es lo contrario de los valores predeterminados de C++, que se copian por valor, a menos que utilices std::move (y que el constructor de movimiento esté definido).

  • Es únicamente el ownership el que se mueve. Si se genera algún código máquina para manipular los datos en sí, se trata de una cuestión de optimización, y esas copias se optimizan de forma agresiva.

  • Los valores simples (como los enteros) se pueden marcar como Copy (consulta las diapositivas posteriores).

  • En Rust, la clonación es explícita (usando clone).

In the say_hello example:

  • Con la primera llamada a say_hello, main deja de tener el ownership de name. Después, ya no se podrá usar name dentro de main.
  • La memoria de heap asignada a name se liberará al final de la función say_hello.
  • main podrá conservar el _ownership_ si pasaname como referencia (&name) y si say_hello` acepta una referencia como parámetro.
  • Por otro lado, main puede pasar un clon de name en la primera llamada (name.clone()).
  • Rust hace que resulte más difícil que en C++ crear copias por error al definir la semántica de movimiento como predeterminada y al obligar a los programadores a clonar sólo de forma explícita.

More to Explore

Defensive Copies in Modern C++

La versión moderna de C++ soluciona este problema de forma diferente:

std::string s1 = "Cpp";
std::string s2 = s1;  // Duplicate the data in s1.
  • Los datos de la stack de s1 se duplican y s2 obtiene su propia copia independiente.
  • Cuando s1 y s2 salen del ámbito, cada uno libera su propia memoria.

Antes de la asignación de copias:

StackHeaps1ptrCpplen3capacity3

Después de la asignación de copia:

StackHeaps1ptrCpplen3capacity3s2ptrCpplen3capacity3

Puntos clave:

  • C++ has made a slightly different choice than Rust. Because = copies data, the string data has to be cloned. Otherwise we would get a double-free when either string goes out of scope.

  • C++ also has std::move, which is used to indicate when a value may be moved from. If the example had been s2 = std::move(s1), no heap allocation would take place. After the move, s1 would be in a valid but unspecified state. Unlike Rust, the programmer is allowed to keep using s1.

  • Unlike Rust, = in C++ can run arbitrary code as determined by the type which is being copied or moved.