¿Las referencias mutables tienen semántica de movimiento?

Resuelto oberblastmeister asked hace 4 años • 0 respuestas
fn main() {
    let mut name = String::from("Charlie");
    let x = &mut name;
    let y = x;       // x has been moved
    say_hello(y);
    say_hello(y);       // but y has not been moved, it is still usable
    change_string(y);
    change_string(y);  

}

fn say_hello(s: &str) {
    println!("Hello {}", s);
}

fn change_string(s: &mut String) {
    s.push_str(" Brown");
}

Cuando lo asigno xa y xse ha movido. Sin embargo, esperaría que algo con semántica de movimiento se moviera cuando lo uso en una función. Sin embargo, todavía puedo usar la referencia después de llamadas posteriores. Tal vez esto tenga que ver con say_hello() tomando una referencia inmutable pero change_string() toma una referencia mutable pero la referencia aún no se mueve.

oberblastmeister avatar Jul 18 '20 02:07 oberblastmeister
Aceptado

Tienes toda la razón tanto en tu razonamiento como en tus observaciones. Definitivamente parece que las cosas deberían suceder como usted las describe. Sin embargo, el compilador aplica aquí algo de magia de conveniencia.

La semántica de movimiento generalmente se aplica en Rust para todos los tipos que no implementan el Copyrasgo. Las referencias compartidas son Copy, por lo que simplemente se copian cuando se asignan o se pasan a una función. Las referencias mutables no lo son Copy, por lo que deben moverse.

Ahí es donde comienza la magia. Siempre que se asigna una referencia mutable a un nombre con un tipo que el compilador ya sabe que es una referencia mutable, la referencia original se vuelve a tomar prestada implícitamente en lugar de moverse. Entonces la llamada a la función

change_string(y);

es transformado por el compilador para significar

change_string(&mut *y);

Se elimina la referencia original y se crea un nuevo préstamo mutable. Este nuevo préstamo se traslada a la función y el préstamo original se libera una vez que regresa la función.

Tenga en cuenta que esta no es una diferencia entre llamadas a funciones y asignaciones. Los nuevos préstamos implícitos ocurren siempre que el compilador ya sabe que el tipo de destino es una referencia mutable, por ejemplo, porque el patrón tiene una anotación de tipo explícita. Entonces, esta línea también crea un nuevo préstamo implícito, ya que la anotamos explícitamente como un tipo de referencia mutable:

let y: &mut _ = x;

Esta llamada a función, por otro lado, mueve (y por lo tanto consume) la referencia mutable y:

fn foo<T>(_: T) {}

[...]
foo(y);

El tipo genérico Taquí no es explícitamente un tipo de referencia mutable, por lo que no se produce ningún nuevo préstamo implícito, aunque el compilador infiera que el tipo es una referencia mutable, tal como en el caso de su tarea let y = x;.

En algunos casos, el compilador puede inferir que un tipo genérico es una referencia mutable incluso en ausencia de una anotación de tipo explícita:

fn bar<T>(_a: T, _b: T) {}

fn main() {
    let mut i = 42;
    let mut j = 43;
    let x = &mut i;
    let y = &mut j;
    bar(x, y);   // Moves x, but reborrows y.
    let _z = x;  // error[E0382]: use of moved value: `x`
    let _t = y;  // Works fine. 
}

Al inferir el tipo del primer parámetro, el compilador aún no sabe que es una referencia mutable, por lo que no se produce ningún nuevo préstamo implícito y xse mueve a la función. Sin embargo, al llegar al segundo parámetro, el compilador ya ha inferido que Tes una referencia mutable, por lo que yimplícitamente lo vuelve a tomar prestado. (Este ejemplo es una buena ilustración de por qué agregar magia del compilador para que las cosas "simplemente funcionen" generalmente es una mala idea. Explícito es mejor que implícito).

Lamentablemente, este comportamiento no está documentado actualmente en la referencia de Rust .

Ver también:

  • Cosas que hace la función de identidad (en Rust)
  • Discusión del tema en el foro de usuarios de Rust.
  • ¿Por qué no se mueve aquí la referencia mutable?
Sven Marnach avatar Jul 17 '2020 20:07 Sven Marnach