WEB3DEV Español

Cover image for Propiedad de Dominio en Rust: 10 Cosas Esenciales que Debes Saber
Juliana Cabeza
Juliana Cabeza

Posted on

Propiedad de Dominio en Rust: 10 Cosas Esenciales que Debes Saber

Aprende el sistema de propiedad de Rust para escribir un código libre de bugs que se ejecute extremadamente rápido. Esta guía explica las 10 reglas de propiedad de Rust con ejemplos reales de código

Introducción

Los lenguajes de programación como C/C++, brindan a los desarrolladores un gran control sobre la gestión de memoria. Sin embargo, esto conlleva un gran riesgo: es fácil cometer errores que pueden resultar en fallos, vulnerabilidades de seguridad o errores en el software.

Algunos problemas comunes que enfrentan los programadores de C/C++ incluyen:

  • Usar memoria después de haberla liberado. Esto conduce a fallas o errores extraños más adelante.
  • Olvidar liberar la memoria cuando ya no se necesita. Esto provoca pérdidas de memoria con el tiempo.
  • Dos partes del código accediendo a la misma memoria, al mismo tiempo. Esto puede causar condiciones de carrera.

Para evitar estos problemas, Rust utiliza un sistema de propiedad. Este sistema agrega algunas reglas que el compilador verifica para garantizar la seguridad de la memoria.

La idea principal es que cada valor en Rust debe tener un propietario. El propietario es responsable de ese valor, gestionando su ciclo de vida, liberándolo, permitiendo el acceso a él, etc.

Al rastrear la propiedad, el compilador de Rust puede garantizar que los valores sean válidos cuando se utilizan, evitar condiciones de carrera de datos y liberar memoria cuando sea necesario. ¡Todo esto sin necesidad de un recolector de basura!

Este modelo de propiedad potencia la seguridad y la velocidad de Rust. Siguiendo algunas reglas de propiedad, tus programas en Rust estarán protegidos de clases enteras de problemas relacionados con la memoria.

Vamos a repasar los 10 superpoderes de propiedad que Rust proporciona:

Cada valor tiene una variable propietaria.

En Rust, cada valor, como una cadena o un entero, tiene un propietario. El propietario es la variable que está enlazada a ese valor. Por ejemplo:

let x = 5;
Enter fullscreen mode Exit fullscreen mode

Aquí, x es el propietario del valor entero 5. La variable x supervisa y gestiona ese valor 5.

Puedes pensar en ello como si x asumiera la propiedad y la responsabilidad sobre ese valor 5. ¡x es ahora el jefe de ese valor!"

Este sistema de propiedad evita situaciones confusas con múltiples variables apuntando al mismo valor. Con la propiedad única, queda claro que x es el único propietario de los datos 5.

Cuando el propietario sale del alcance, el valor se descarta

Cuando la variable propietaria sale del alcance, Rust llamará a la función drop para el valor y lo limpiará:

{
  let y = 5; // y  es el propietario de 5
} // y sale del alcance y 5 se descarta

Enter fullscreen mode Exit fullscreen mode

El alcance se refiere al bloque en el que una variable es válida. En el ejemplo anterior, y solo existe dentro de las llaves {}. Una vez que la ejecución sale de ese bloque, y desaparece y el valor 5 se descarta.

Esta liberación automática de datos evita fugas de memoria. Tan pronto como el propietario y desaparece, Rust limpia el valor. ¡No más preocupaciones por punteros colgantes o sobrecarga de memoria!

Sólo puede haber un propietario a la vez

Rust impone propiedad única para cada valor. Esto evita esquemas costosos de conteo de referencias:

let z = 5; // z tiene 5
let x = z; // la propiedad de z se transfiere a x 
// ¡z ya no tiene 5!

Enter fullscreen mode Exit fullscreen mode

En este ejemplo, transferir la propiedad de z a x es barato. Algunos lenguajes utilizan el conteo de referencias, donde varias variables pueden apuntar a un valor, pero esto conlleva una sobrecarga.

Con la propiedad única, Rust solo actualiza una variable propietaria interna para transferir la propiedad de z a x. Sin actualizaciones costosas de contadores.

Cuando el propietario se copia, los datos se mueven.

Asignar una variable propietaria a una nueva variable mueve los datos:

let s1 = "hello".to_string(); // s1 tiene "hello"
let s2 = s1; // la propiedad de s1 se transfiere a s2   
             // s1 ya no puede usar "hello"

Enter fullscreen mode Exit fullscreen mode

Aquí creamos el string 'hello' y la vinculamos a s1. Luego, asignamos s1 a una nueva variable s2.

Esto transfiere la propiedad de s1 a s2. s1 ya no es el propietario del string. Los datos en sí no se copiaron, solo se movió la propiedad.

Esto evita copias costosas accidentales. Para realmente copiar los datos, debes usar el método clone() de Rust para dejar la intención clara.

La propiedad puede ser prestada a través de referencias

Podemos crear variables de referencia que prestan la propiedad:

let s = "hello".to_string(); // s tiene"hello"  
let r = &s; // r toma prestado inmutablemente de s
            // s todavía tiene "hello"
println!("{}", r); // imprime "hello"
Enter fullscreen mode Exit fullscreen mode

El operador & crea una referencia r que toma prestada la propiedad de s para este ámbito.

Piensa en r como si estuviera tomando prestados temporalmente los datos que s posee. s aún conserva la propiedad total de los datos. r solo tiene permiso para leer la cadena 'hello'.

Las referencias mutables tienen acceso exclusivo

Solo puede haber una referencia mutable a los datos a la vez:

let mut s = "hello".to_string();  

let r1 = &mut s; // r1 presta s inmutablemente

let r2 = &mut s; // ¡error!
Enter fullscreen mode Exit fullscreen mode

Esto evita condiciones de carrera en tiempo de compilación. La referencia mutable r1 tiene acceso exclusivo de escritura a s, por lo que no se permite ninguna otra referencia hasta que r1 termine.

Esto te protege de errores de concurrencia sutiles al hacer que el acceso simultáneo a los datos sea imposible.

Las referencias deben tener una duración menor que sus propietarios.

Las referencias deben tener vidas más cortas que lo que están prestando:

{
  let r;               
  let s = "hello".to_string();

  r = &s; // ¡error! r no vive lo suficiente

} // s se descarta aquí
Enter fullscreen mode Exit fullscreen mode

Aquí, r sale del alcance antes que s. ¡Entonces r estaría haciendo referencia a los datos de s después de que s se haya descartado!

Rust evita errores de uso después de la liberación imponiendo esta regla de que las referencias no pueden vivir más que sus propietarios.

Structs pueden ser pasados por movimiento o préstamo

Podemos transferir o prestar la propiedad de los datos de un struct:

struct User {
  name: String,
  age: u32  
}

let user1 = User {
  name: "John".to_string(),
  age: 27
}; // usuario1 es el propietario de Struct  

let user2 = user1; // propiedad transferida al user2
                   // user1 ya no puede usar esto.

let borrow = &user1; // prestar a Struct por referencia 
                     // user1 sigue siendo el propietario de los datos.
Enter fullscreen mode Exit fullscreen mode

Structs agrupan datos relacionados, pero las reglas de propiedad aún se aplican a sus campos.

Podemos transferir la propiedad de un struct a funciones y threads, o prestarla de forma inmutable. Las mismas reglas de propiedad única y préstamo hacen que el uso de struct sea seguro.

La propiedad funciona de la misma manera en el heap.

La propiedad se aplica a datos asignados en el heap:

let s1 = String::from("hello"); // s1 en la pila tiene datos del  heap

let s2 = s1.clone(); // datos del heap copiados a una nueva ubicación
                     // s1 y s2 tienen datos separados

let r = &s1; //"r presta los datos del heap de s1 de forma inmutable." 
             // s1 todavía posee los datos del  heap


Enter fullscreen mode Exit fullscreen mode

Aquí, s1 se aloja en la pila, pero contiene un String que apunta al texto asignado en el heap. Las mismas reglas de propiedad se aplican, incluso cuando se trata del heap.

Rust evita liberaciones duplicadas o uso después de la liberación, incluso cuando se trabaja con punteros. El sistema de propiedad mantiene seguras las asignaciones en el heap.

La propiedad permite una competencia segura

La propiedad impulsa la intrépida competencia de Rust:

`use std::thread;

let v = vec![1, 2, 3]; 

let handle = thread::spawn(move || {
  println!("Here's a vector: {:?}", v);
});

handle.join().unwrap();

Enter fullscreen mode Exit fullscreen mode

Transferimos la propiedad de v a la thread creada usando un cierre move. Esto evita el acceso concurrente a v desde múltiples threads.

El sistema de propiedad hace que la concurrencia sea segura y sencilla en Rust. No es necesario utilizar bloqueos porque el compilador impone la propiedad única.

Conclusión

El sistema de propiedad de Rust está diseñado para mantener tu código seguro y rápido. Al imponer algunas reglas clave, se eliminan clases enteras de bugs de memoria.

Algunas de las lecciones clave sobre propiedad:

  • Cada valor en Rust tiene una variable propietaria responsable de ese valor.
  • Cuando el propietario desaparece, el valor se limpia automáticamente. ¡No más fugas de memoria!
  • Los valores solo pueden tener un propietario a la vez. Esto evita confusiones.
  • Las referencias pueden prestar la propiedad temporalmente de manera segura.
  • El compilador verifica la validez de las referencias para prevenir punteros pendientes.
  • Las reglas de propiedad evitan condiciones de carrera y hacen que la concurrencia sea fácil.

Entonces, si la propiedad te obliga a pensar en la gestión de la memoria, es por una buena razón, ¡elimina un montón de bugs potenciales!

El sistema de propiedad es una parte fundamental de lo que hace que Rust sea tan confiable y rápido. Seguir las reglas de propiedad de Rust mantendrá tu código seguro, incluso cuando tus programas se vuelvan grandes.

Así que adopta las verificaciones en tiempo de compilación de Rust como guías útiles, no como restricciones. Tu yo futuro te lo agradecerá cuando tu programa Rust se ejecute sin problemas, sin bloqueos ni fallos de seguridad.

Artículo original publicado por JSDevJournal. Traducido por Juliana Cabeza.

Discussion (0)