Semántica de movimiento
Una asignación transferirá su ownership entre variables:
fn main() { let s1: String = String::from("¡Hola!"); let s2: String = s1; println!("s2: {s2}"); // println!("s1: {s1}"); }
- La asignación de
s1
as2
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
:
Después de mover a s2
:
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!("Hola {name}") } fn main() { let name = String::from("Alice"); say_hello(name); // say_hello(name); }
-
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
).
En el ejemplo de say_hello
:
- Con la primera llamada a
say_hello
,main
deja de tener el ownership dename
. Después, ya no se podrá usarname
dentro demain
. - La memoria de heap asignada a
name
se liberará al final de la funciónsay_hello
. - main
podrá conservar el _ownership_ si pasa
namecomo referencia (
&name) y si
say_hello` acepta una referencia como parámetro. - Por otro lado,
main
puede pasar un clon dename
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.
Más información
Copias Defensivas en C++ Moderno
La versión moderna de C++ soluciona este problema de forma diferente:
std::string s1 = "Cpp";
std::string s2 = s1; // Duplica los datos en s1.
- Los datos de la stack de
s1
se duplican ys2
obtiene su propia copia independiente. - Cuando
s1
ys2
salen del ámbito, cada uno libera su propia memoria.
Antes de la asignación de copias:
Después de la asignación de copia:
Puntos clave:
-
C++ ha tomado una decisión algo distinta a Rust. Puesto que
=
copia los datos, los datos de cadena deben clonarse. De lo contrario, obtendríamos un error double free si alguna de las cadenas saliera fuera del ámbito. -
C++ también tiene
std::move
, que se usa para indicar cuándo se puede mover un valor. Si el ejemplo hubiera sidos2 = std::move(s1)
, no se llevaría a cabo ninguna asignación de montículo. Después del movimiento,s1
tendría un estado válido, pero no especificado. A diferencia de Rust, el programador puede seguir utilizandos1
. -
A diferencia de Rust, en C++ se puede ejecutar código arbitrario con
=
según el tipo que se vaya a copiar o mover.