Copia

Algunos tipos de Rust son muy simples. Se almacenan todos en la pila ya que el compilador conoce su tamaño. Esto significa que son fáciles de copiar, por lo que el compilador siempre los copia cuando se envían a una función. Son valores de tamaño fijo, conocido y pequeño. En estos casos, no hay necesidad de preocuparse por quién es el dueño de estos tipos de dato. A estos tipos, se los denomina tipos copia (Copy Types, en inglés).

Estos tipos simples incluyen a los enteros, flotantes, booleanos (true -verdadero- y false -falso-) y char.

Para que los tipos se puedan copiar tienen que implementar la posibilidad de copia (copy). Se puede consultar la documentación de cada tipo para conocerlo. Por ejemplo, esta es la documentación del tipo char:

https://doc.rust-lang.org/std/primitive.char.html

A la izquierda de esta documentación se puede ver Trait Implementations. Se muestran, entre otras: Copy, Debug y Display. Lo que permite conocer que char puede:

  • Copiarse cuando se pasa como parámetro a una función (Copy).
  • Puede usar {} para imprimir (Display).
  • Puede usar {:?} para imprimir (Debug).
fn prints_number(numero: i32) { // Esta función no tiene ->, no devuelve ningún valor
                             // Si el número no se copiara, esta función se haría au propietaria 
                             // y no se podría volver a usar
    println!("{}", numero);
}

fn main() {
    let mi_numero = 8;
    prints_number(mi_numero); // Imprime 8. prints_number obtiene una copia del número
    prints_number(mi_numero); // Imprime 8 de nuevo.
                              // No hay problema ya que mi_numero es un tipo que se copia
}

Sin embargo, si se revisa la documentación de String se ve que no es un tipo que se copie.

https://doc.rust-lang.org/std/string/struct.String.html

A la izquierda de esta documentación se puede ver Trait Implementations, en orden alfabético. En la C no está Copy. Lo que sí aparece es Clone, que es similar a *Copy, solo que es necesario invocarlo expresamente con el método clone(). Es decir, que no se clona por sí mismo, se tiene que pedir expresamente.

En el siguiente ejemplo, prints_country() imprime el nombre del país, que es de tipo String. Se quiere, como en el caso anterior, imprimir dos veces, pero no es posible:

fn prints_country(country_name: String) {
    println!("{}", country_name);
}

fn main() {
    let country = String::from("Kiribati");
    prints_country(country);
    prints_country(country); // ⚠️
}

El mensaje es autoexplicativo:

error[E0382]: use of moved value: `country`
 --> src\main.rs:4:20
  |
2 |     let country = String::from("Kiribati");
  |         ------- move occurs because `country` has type `std::string::String`, which does not implement the `Copy` trait
3 |     prints_country(country);
  |                    ------- value moved here
4 |     prints_country(country);
  |                    ^^^^^^^ value used here after move

Dice que String no implementa el rasgo necesario para copiar Copy trait. Sin embargo, se ha visto que sí implementa el rasgo Clone, por lo que se puede añadir .clone() al código para generar de forma expresa una copia del valor. De este modo, se puede enviar un clon del valor a la función. Así, después de llamar a la función, la variable country continúa vigente y se puede utilizar.

fn prints_country(country_name: String) {
    println!("{}", country_name);
}

fn main() {
    let country = String::from("Kiribati");
    prints_country(country.clone()); // crea un clon y lo pasa a la función. country sigue vigente
    prints_country(country);
}

Evidentemente, si el String es muy largo, generar un clon requiere el uso de mucha memoria. Una cadena de texto String puede ser de la longitud de un libro entero, y cada vez que se llama a .clone() se genera una copia completa. Por eso, es recomendable el uso de & para utilizar una referencia cuando sea posible. Por ejemplo, el código siguiente añade una cadena de texto &str en una String y después crea un clon cada vez que se utiliza en una función:

fn get_length(input: String) { // Se apropia de la cadena de texto
    println!("Tiene una longitud de {} palabras.", input.split_whitespace().count()); // la divide para contar el número de palabras
}

fn main() {
    let mut my_string = String::new();
    for _ in 0..50 {
        my_string.push_str("Aquí van palabras en español "); // añade las palabras
        get_length(my_string.clone()); // obtiene un nuevo clon cada vez
    }
}

Lo que imprime es:

Tiene una longitud de 5 palabras.
Tiene una longitud de 10 palabras.
...
Tiene una longitud de 250 palabras

Esto genera 50 clones. El siguiente código cumple la misma función usando referencias y es mucho más eficiente:

fn get_length(input: &String) {
    println!("Tiene una longitud de {} palabras.", input.split_whitespace().count());
}

fn main() {
    let mut my_string = String::new();
    for _ in 0..50 {
        my_string.push_str("Aquí van palabras en español ");
        get_length(&my_string);
    }
}

Con el código anterior no se genera ningún clon.

Variables sin valores

Una variables sin valor está sin inicializar. Para crear una variable en este estado se usa let y el nombre de la variable:

fn main() {
    let my_variable; // ⚠️
}

Rust no compila si hay alguna variable sin inicializar.

Sin embargo, en ocasiones pueden ser útiles. Un claro ejemplo es cuando:

  • Se tiene un bloque de código en el que se genera el valor necesario para la variable y...
  • ...la variable tiene que sobrevivir fuera del bloque de código.
fn loop_then_return(mut counter: i32) -> i32 {
    loop {
        counter += 1;
        if counter % 50 == 0 {
            break;
        }
    }
    counter
}

fn main() {
    let my_number;

    { // Este bloque es innecesario, pero se crea para documentar caso
        let number = {
            // Aquí podria haber mucho código
            // para generar un valor, por ejemplo:
            57
        };

        my_number = loop_then_return(number);
    }

    println!("{}", my_number);
}

Este ejemplo imprime 100.

Se observa que my_number se declarón en la función main(), por lo que su tiempo de vida dura hasta su finalización. Y obtiene su valor dentro del bucle loop. El valor pasa a ser propiedad de my_number antes de salir del bloque.

Si se hubiera declarado y asignado el valor en la misma línea con let my_number = loop_then_return(number) dentro del bloque, la variable hubiera desaparecido con el bloque.

De forma simplificada, ayuda a verlo el sustituir la función por su valor de retorno, 100. Se ve en el siguiente código:

fn main() {
    let my_number;
    {
        my_number = 100;
    }

    println!("{}", my_number);
}

Es casi como escribir let my_number = { 100 };.

Se debe observar que my_number no es mut. No se le asigna un valor hasta que se asigna el 100, por lo que no ha cambiado de valor. En el fondo el código real para my_number es solo let my_number = 100;.