Paso de referencias a funciones

Puedes ver este capítulo en Youtube en inglés: referencias inmutables y referencias mutables

Las referencias son muy útiles en las funciones. La regla de Rust para todos los valores es que un valor solo puede tener una variable propietario.

Este código no funcionará:

fn print_pais(pais_nombre: String) {
    println!("{}", pais_nombre);
}

fn main() {
    let pais = String::from("Austria");
    print_pais(pais); // Se imprime "Austria"
    print_pais(pais); // ⚠️ Se intenta de nuevo, pero no funciona
}

Devuelve el siguiente error:

error[E0382]: use of moved value: `pais`
 --> src/main.rs:8:16
  |
6 |     let pais = String::from("Austria");
  |         ---- move occurs because `pais` has type `String`, which does not implement the `Copy` trait
7 |     print_pais(pais); // Se imprime "Austria"
  |                ---- value moved here
8 |     print_pais(pais); // ⚠️ Se intenta de nuevo, pero no funciona
  |                ^^^^ value used here after move

La variable pais ya no existen en la última línea. El funcionamiento es el siguiente:

  • Paso 1: Se crea el valor de tipo String cuyo dueño es la variable pais.
  • Paso 2: Se pasa pais a la función print_pais. Es una función que no tiene -> en su declaración, por lo que no retorna ningún valor. Al hacer la llamada, la variable pais_nombre (parámetro) es la nueva dueña del valor. Después de que esta función finaliza, pais_nombre desaparece, como es la dueña del valor, este también se destruye.
  • Paso 3: Se intenta pasar por segunda vez pais a la función `print_pais, pero ya no existe ya que dejó de ser dueña del valor y el valor despareció dentro de la primera llamada a la función.

Se podría hacer que la función print_pais devolviera de nuevo el valor String, pero es poco ortodoxo.

fn print_pais(pais_nombre: String) -> String {
    println!("{}", pais_nombre);
    pais_nombre // se devuelve el valor
}

fn main() {
    let pais = String::from("Austria");
    let pais = print_pais(pais); // Es necesario crear una nueva variable para recuperar el valor
    print_pais(pais);
}

Ahora sí funciona e imprime:

Austria
Austria

Es mucho mejor evitar que la variable print_nombre, parámetro de la función, sea dueña del valor, solamente se "presta", sin que la variable pais de la función main deje de ser su dueño.

fn print_pais(pais_nombre: &String) {
    println!("{}", pais_nombre);
}

fn main() {
    let pais = String::from("Austria");
    print_pais(&pais); // Se imprime "Austria"
    print_pais(&pais); // Se intenta de nuevo y funciona correctamente
}

En este caso print_pais() toma una referencia a una String: &String. Cuando se llama print_pais() se pasa una referencia con &pais. Esto significa que "la función puede acceder al valor, pero no se hace dueña de él".

A continuación, se muestra un ejemplo similar para observar el comportamiento de las referencias modificables (mutables).

fn añade_hungria(pais_nombre: &mut String) { // se pasa una referencia mutable
    pais_nombre.push_str("-Hungría"); // push_str() añade un &str a un String
    println!("Ahora dice: {}", pais_nombre);
}

fn main() {
    // es importante que la variable se declare
    // como mutable para poder crear referencias mutables
    let mut pais = String::from("Austria");
    añade_hungria(&mut pais); // hay que pasar la referencia mutable.
    println!("Y el valor se ha modificado aquí: {}", pais);
}

Que imprime:

Ahora dice: Austria-Hungría
Y el valor se ha modificado aquí: Austria-Hungría

En resumen:

  • fn nombre_de_funcion(variable: String) toma un String y se hace dueño de él.
  • fn nombre_de_funcion(variable: &String) toma prestado un String y puede acceder a su valor.
  • fn nombre_de_funcion(variable: &mut String) toma prestado un String, puede acceder a su valor y modificarlo.

El siguiente ejemplo puede parecer similar, pero es muy diferente. Sirve para mostrar cómo quien se hace dueño de un objeto puede decidir que sea modificable, aunque anteriormente no lo fuese.

fn main() {
    let pais = String::from("Austria"); // pais no es mutable, ni referencia
    añadir_hungria(pais);
}

fn añadir_hungria(mut pais: String) { // añadir_hungria declara su parámetro como mutable
    pais.push_str("-Hungría");
    println!("{}", pais);
}

La función añadir_hungria se hace dueña del valor en su variable mut pais. A partir de ahí, puede hacer con este valor lo que quiera.

Si se recuerda el ejemplo anterior sobre el empleado, su jefe y la presentación en powerpoint, esta es la situación en la que el empleado le da el control completo al jefe. El empleado no puede volver a tocar la presentación y el jefe puede hacer lo que quiera con ella.