Referencias y Prestamos

El problema con la tupla de código en el Listado 4-5 es que tenemos que devolver el String a la función que lo llama para que podamos seguir usando el String después de la llamada a calcular_longitud, porque el String se movió a calcular_longitud. En lugar de eso, podemos proporcionar una referencia al valor String. Una referencia es como un puntero en que es una dirección que podemos seguir para acceder a los datos almacenados en esa dirección; esos datos son propiedad de otra variable. A diferencia de un puntero, una referencia garantiza que apunte a un valor válido de un tipo particular para la vida de esa referencia.

Aquí está cómo definirías y usarías una función calcular_longitud que tiene una referencia a un objeto como parámetro en lugar de tomar la propiedad del valor.

Nombre de archivo: src/main.rs

fn main() {
    let s1 = String::from("hola");

    let len = calcular_longitud(&s1);

    println!("La longitud de '{s1}' es {len}.");
}

fn calcular_longitud(s: &String) -> usize {
    s.len()
}

Primero, ten en cuenta que todo el código de la tupla en la declaración de la variable y el valor de retorno de la función ha desaparecido. En segundo lugar, observe que pasamos &s1 a calcular_longitud y, en su definición, tomamos &String en lugar de String. Este signo ampersands (&) representa referencia, y te permiten referirte a algún valor sin tomar la propiedad de él. La Figura 4-5 representa este concepto.

Tres tablas: la tabla para s contiene solo un puntero a la tabla para s1. La tabla para s1 contiene los datos de la pila para s1 y apunta a los datos de la cadena en la pila. Tres tablas: la tabla para s contiene solo un puntero a la tabla para s1. La tabla para s1 contiene los datos de la pila para s1 y apunta a los datos de la cadena en la pila. s nombre valor puntero s1 nombre valor puntero longitud 4 capacidad 4 indice valor 0 h 1 o 2 l 3 a

Figura 4-5: Un diagrama de &String s apuntando a String s1

Nota: Lo opuesto a la referencia usando & es desreferenciar, que se logra con el operador de desreferencia, *. Veremos algunos usos del operador de desreferencia en el Capítulo 8 y discutiremos detalles de la desreferenciación en el Capítulo 15.

Vamos a echar un vistazo más de cerca a la llamada de función aquí:

fn main() {
    let s1 = String::from("hola");

    let len = calcular_longitud(&s1);

    println!("La longitud de '{s1}' es {len}.");
}

fn calcular_longitud(s: &String) -> usize {
    s.len()
}

La sintaxis &s1 nos permite crear una referencia que se refiere al valor de s1 pero sin ser el propietario. Por este motivo, el valor al que apunta no se descartará cuando la referencia deje de usarse.

Del mismo modo, la firma de la función usa & para indicar que el tipo del parámetro s es una referencia. Vamos a agregar algunas anotaciones:

fn main() {
    let s1 = String::from("hola");

    let len = calcular_longitud(&s1);

    println!("La longitud de '{s1}' es {len}.");
}

fn calcular_longitud(s: &String) -> usize { // es una referencia a un String
    s.len()
} // Aquí, s sale de ámbito. Pero como no tiene el ownership/la propiedad sino 
  // que s es solo un prestamo, no se destruye, se regresa al propietario, s1.

El contexto de ejecución en el que la variable s es válida es el mismo que el contexto de ejecución de cualquier parámetro de función, pero el valor al que apunta la referencia no se descarta cuando s deja de usarse, porque s no tiene la propiedad. Cuando las funciones tienen referencias como parámetros en lugar de los valores reales, no necesitaremos devolver los valores para devolver la propiedad, porque nunca tuvimos la propiedad.

Llamamos a la acción de crear una referencia prestar (borrowing en ingles). Como en la vida real, si una persona posee algo, puedes pedir prestado. Cuando termines, tienes que devolverlo. No lo posees.

Entonces, ¿qué pasa si intentamos modificar algo que estamos prestando? Prueba el código en el Listado 4-6. Spoiler alert: ¡no funciona!

Nombre de archivo: src/main.rs

fn main() {
    let s = String::from("hola");

    modificar(&s);
}

fn modificar(un_string: &String) {
    un_string.push_str(", mundo");
}

Listado 4-6: Intentando modificar un valor prestado

Aquí está el error:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*un_string` as mutable, as it is behind a `&` reference
 --> src/main.rs:8:5
  |
8 |     un_string.push_str(", world");
  |     ^^^^^^^^^^^ `un_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
  |
help: consider changing this to be a mutable reference
  |
7 | fn modificar(un_string: &mut String) {
  |                         +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Al igual que las variables son inmutables por defecto, también lo son las referencias. No se nos permite modificar algo al que tenemos una referencia.

Referencias Mutables

Podemos arreglar el código del Listado 4-6 para permitirnos modificar un valor prestado con solo unos pequeños cambios que usen, en su lugar, una referencia mutable:

Nombre de archivo: src/main.rs

fn main() {
    let mut s = String::from("hola");

    modificar(&mut s);
}

fn modificar(un_string: &mut String) {
    un_string.push_str(", mundo");
}

Primero cambiamos s a mut. Luego creamos una referencia mutable con &mut s donde llamamos a la función modificar, y actualizamos la firma de la función para aceptar una referencia mutable con un_string: &mut String. Esto hace muy claro que la función modificar mutará el valor que presta.

Las referencias mutables tienen una gran restricción: si tienes una referencia mutable a un valor, no puedes tener otras referencias a ese valor. Este código que intenta crear dos referencias mutables a s fallará:

Nombre de archivo: src/main.rs

fn main() {
    let mut s = String::from("hola");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}

Aquí está el error:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time
 --> src/main.rs:5:14
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 |
7 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

Este error dice que este código es inválido porque no podemos prestar s como mutable más de una vez. El primer préstamo mutable está en r1 y debe durar hasta que se use en el println!, pero entre la creación de esa referencia mutable y su uso, intentamos crear otra referencia mutable en r2 que presta los mismos datos que r1.

La restricción que impide múltiples referencias mutables a los mismos datos al mismo tiempo permite la mutación pero de una manera muy controlada. Es algo con lo que los nuevos Rustaceans luchan porque la mayoría de los lenguajes te permiten mutar cuando quieras. El beneficio de tener esta restricción es que Rust puede prevenir las carreras de datos en tiempo de compilación. Una carrera de datos es similar a una condición de carrera y ocurre cuando ocurren estos tres comportamientos:

  • Dos o más punteros acceden a los mismos datos al mismo tiempo.
  • Al menos uno de los punteros se está utilizando para escribir en los datos.
  • No hay ningún mecanismo que se esté utilizando para sincronizar el acceso a los datos.

Las carreras de datos causan un comportamiento indefinido y pueden ser difíciles de diagnosticar y corregir cuando intentas rastrearlas en tiempo de ejecución; ¡Rust evita este problema al negarse a compilar código con carreras de datos!

Como siempre, podemos usar llaves para crear un nuevo contexto de ejecución, permitiendo múltiples referencias mutables, solo no simultáneas:

fn main() {
    let mut s = String::from("hola");

    {
        let r1 = &mut s;
    } // r1 se sale de su ámbito aquí, por lo que no hay problema 
      // si creamos otra referencia mutable

    let r2 = &mut s;
}

Rust impone una regla similar para combinar referencias mutables e inmutables. Este código da como resultado un error:

fn main() {
    let mut s = String::from("hola");

    let r1 = &s; // no hay problema
    let r2 = &s; // no hay problema
    let r3 = &mut s; // ¡ UN GRAN PROBLEMA !

    println!("{}, {}, y {}", r1, r2, r3);
}

Aquí está el error:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:14
  |
4 |     let r1 = &s; // no hay problema
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // no hay problema
6 |     let r3 = &mut s; // ¡ UN GRAN PROBLEMA !
  |              ^^^^^^ mutable borrow occurs here
7 |
8 |     println!("{}, {}, y {}", r1, r2, r3);
  |                                -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` (bin "ownership") due to 1 previous error

¡Uf! También no podemos tener una referencia mutable mientras tenemos una inmutable al mismo valor.

¡Los usuarios de una referencia inmutable no esperan que el valor cambie repentinamente debajo de ellos! Sin embargo, se permiten múltiples referencias inmutables porque nadie que solo está leyendo los datos tiene la capacidad de afectar la lectura de los datos de nadie más.

Tenga en cuenta que el contexto de ejecución de una referencia comienza desde donde se introduce y continúa hasta la última vez que se usa la referencia. Por ejemplo, este código se compilará porque el último uso de las referencias inmutables, el println!, ocurre antes de que se introduzca la referencia mutable:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // no hay problema
    let r2 = &s; // no hay problema
    println!("{r1} y {r2}");
    // variables r1 y r2 no se usaran más a partir de aquí

    let r3 = &mut s; // no hay problema
    println!("{r3}");
}

Los contextos de ejecución de las referencias inmutables r1 y r2 terminan después del println! donde se usan por última vez, que es antes de que se cree la referencia mutable r3. Estos contextos de ejecución no se superponen, por lo que este código está permitido: ¡el compilador puede decir que la referencia ya no se está utilizando en un punto antes del final del ámbito!

Aunque los errores de préstamo a veces pueden ser frustrantes, recuerda que es el compilador de Rust que señala un error potencial temprano (en tiempo de compilación en lugar de en tiempo de ejecución) y te muestra exactamente dónde está el problema. Entonces no tienes que rastrear por qué tus datos no son lo que pensabas que eran.

Referencias colgantes

En lenguajes con punteros, es fácil crear accidentalmente un puntero colgante: un puntero que hace referencia a una ubicación en la memoria que puede haber sido otorgada a otra persona, al liberar algo de memoria mientras se preserva un puntero a esa memoria. En Rust, por el contrario, el compilador garantiza que las referencias nunca serán referencias colgantes: si tiene una referencia a algún dato, el compilador asegurará que los datos no salgan de contexto de ejecución antes de que la referencia a los datos lo haga.

Intentemos crear una referencia colgante para ver cómo Rust los previene con un error de tiempo de compilación:

Filename: src/main.rs

Nombre de archivo: src/main.rs

fn main() {
    let referencia_a_la_nada = colgar();
}

fn colgar() -> &String {
    let s = String::from("hola");

    &s
}

Aquí está el error:

$ cargo run
   Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0106]: missing lifetime specifier
 --> src/main.rs:5:16
  |
5 | fn colgar() -> &String {
  |                ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
  |
5 | fn colgar() -> &'static String {
  |                 +++++++
help: instead, you are more likely to want to return an owned value
  |
5 - fn dangle() -> &String {
5 + fn dangle() -> String {
  |

error[E0515]: cannot return reference to local variable `s`
 --> src/main.rs:8:5
  |
8 |     &s
  |     ^^ returns a reference to data owned by the current function

Some errors have detailed explanations: E0106, E0515.
For more information about an error, try `rustc --explain E0106`.
error: could not compile `ownership` (bin "ownership") due to 2 previous errors

Este mensaje de error se refiere a una característica que aún no hemos cubierto: los tiempos de vida. Discutiremos los tiempos de vida en detalle en el Capítulo 10. Pero, si ignora las partes sobre los tiempos de vida, el mensaje contiene la clave para saber por qué este código es un problema:

this function's return type contains a borrowed value, but there is no value
for it to be borrowed from

Se traduciría algo así como:

el tipo de retorno de la función contiene un valor prestado, pero no hay ningún 
valor que pueda ser prestado
```

<span class="filename">Nombre de archivo: src/main.rs</span>

```rust,ignore,does_not_compile
# fn main() {
#     let referencia_a_la_nada = colgar();
# }
# 
fn colgar() -> &String { // colgar retorna una referencia a un String

    let s = String::from("hola"); // s es un nuevo String

    &s // retornamos una referencia a la String, s
} // Aquí, s sale de ámbito y se libera su memoria. 
  // ¡Pero retornamos una referencia a ella!
  // ¡Peligro! ¡Esta referencia apunta a memoria que ya no existe!

Porque s se crea dentro de colgar, cuando el código de colgar finaliza, s se desalocará. Pero intentamos devolver una referencia a él. Eso significa que esta referencia estaría apuntando a una String inválida. ¡Eso no está bien! Rust no nos dejará hacer esto.

La solución aquí es devolver la String directamente:

fn main() {
    let string = no_colgante();
}

fn no_colgante() -> String {
    let s = String::from("hola");

    s
}

Esto funciona sin problemas. La propiedad se mueve fuera y nada se desaloca.

Las reglas de las referencias

Repasemos lo que hemos discutido sobre las referencias:

  • En cualquier momento dado, puedes tener o bien una referencia mutable o cualquier número de referencias inmutables.
  • Las referencias deben ser siempre válidas.

A continuación, veremos un tipo diferente de referencia: los slices.