Rc
Rc
significa "contador de referencias". Ya se ha visto que en Rust cada valor solo puede ter un dueño. Por eso, el siguiente código no funciona:
fn takes_a_string(input: String) { println!("It is: {}", input) } fn also_takes_a_string(input: String) { println!("It is: {}", input) } fn main() { let user_name = String::from("User MacUserson"); takes_a_string(user_name); also_takes_a_string(user_name); // ⚠️ }
Después de que takes_a_string
reciba user_name
, no se puede volver a usar. En este caso, se podría solventar utilizando user_name.clone()
. Pero en ocasiones, un valor forma parte de un struct y puede que no se pueda clonar ese struct. O puede que sea un valor de gran tamaño que no sea eficiente clonar. Por estas razones existe Rc
, que sirve para permitir que un valor tenga más de un dueño de forma simultánea. Rc
anota quienes tienen la propiedad y cuántos. Posteriormente, cuando el número de dueños baja a cero, el valor asociado se liberará.
En el siguiente ejemplo se usa Rc
. Se crean dos struct
: uno denominado City
y otro CityData
. City
contiene la información de una ciudad y CityData
reúne todas las ciudades usando Vec
.
#[derive(Debug)] struct City { name: String, population: u32, city_history: String, } #[derive(Debug)] struct CityData { names: Vec<String>, histories: Vec<String>, } fn main() { let calgary = City { name: "Calgary".to_string(), population: 1_200_000, // Pretend that this string is very very long city_history: "Calgary began as a fort called Fort Calgary that...".to_string(), }; let canada_cities = CityData { names: vec![calgary.name], // This is using calgary.name, which is short histories: vec![calgary.city_history], // But this String is very long }; println!("Calgary's history is: {}", calgary.city_history); // ⚠️ }
Esto no funciona debido a que canada_cities
es el dueño de los datos al final y calgary
ya no lo es. Por lo que el error es el siguiente:
error[E0382]: borrow of moved value: `calgary.city_history`
--> src/main.rs:27:42
|
24 | histories: vec![calgary.city_history], // But this String is very long
| -------------------- value moved here
...
27 | println!("Calgary's history is: {}", calgary.city_history); // ⚠️
| ^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
|
= note: move occurs because `calgary.city_history` has type `String`, which does not implement the `Copy` trait
Se podría clonar el nombre con names: vec![calgary.name.clone()]
, pero no se desea con city_history
debido a que puede ser un texto muy largo. Por ello, se usa Rc
. Para ello, se debe declarar su uso previamente:
use std::rc::Rc; fn main() {}
Y después, usar Rc
como tipo de la variable que se quiera usar:
´´´rust use std::rc::Rc;
#[derive(Debug)]
struct City {
name: String,
population: u32,
city_history: Rc
#[derive(Debug)]
struct CityData {
names: Vec
fn main() {} ´´´
Para añadir una nueva referencia, se debe usar clone
sobre el elemento Rc
. En los Rc
, esta función solo clona el puntero y no duplica la variable contenida. Por ello, su coste es mínimo.
En el ejemplo anterior, al clonar la historia de una ciudad, esta tendrá dos dueños. Se puede comprobar el número de dueños en un momento dado mediante Rc::strong_count(&item)
. El código quedará así:
use std::rc::Rc; #[derive(Debug)] struct City { name: String, population: u32, city_history: Rc<String>, // String dentro de un Rc } #[derive(Debug)] struct CityData { names: Vec<String>, histories: Vec<Rc<String>>, // Un Vec de Strings dentro de Rcs } fn main() { let calgary = City { name: "Calgary".to_string(), population: 1_200_000, city_history: Rc::new("Calgary began as a fort called Fort Calgary that...".to_string()), // Rc::new() para crear el Rc }; let canada_cities = CityData { names: vec![calgary.name], histories: vec![calgary.city_history.clone()], // .clone() para incrementar la cuenta }; println!("Calgary's history is: {}", calgary.city_history); println!("{}", Rc::strong_count(&calgary.city_history)); let new_owner = calgary.city_history.clone(); }
Este código imprime 2
como el número de dueños de la historia de Calgary. Como la última línea de código añade otro dueño, new_owner
, si se imprimiera después la cuenta de dueños, se mostraría ya 3
.
Se observa que esta función habla de strong pointers (punteros fuertes). ¿Existen los punteros débiles? Sí, existen. Se utilizan cuando se quiere evitar referencias circulares entre elementos. Por ejemplo, si dos Rc
se apuntan entre sí, si ambos punteros son fuertes, nunca alcanzarán la cuenta de cero dueños, ya que cada uno es dueño del otro, y no se podrán liberar. Rc
mantiene la cuenta de los punteros débiles, pero no la tiene en cuenta para liberar la memoria. Es decir, cuando un elemento Rc
no tiene punteros fuertes, se libera su espacio aunque la cuenta de punteros débiles no sea cero.
Se puede utilizar Rc::downgrade(&item)
en lugar de Rc::clone(&item)
para construir una referencia débil. Para ver la cuenta de referencias débiles se usa Rc::weak_count(&item)
.