Share data using Rc<T>

In this section, we will start with a problem. We will try to implement the list in the diagram:

In the figure, listing A contains 5, followed by 10, listing B starts at 3, and listing C starts at 4. B and C will be joined to list A containing 5 and 10. In other words, the two lists try to share the 5 and 10 contained in list A.

Using Box

Let’s try using the Box type we’ve already learned:

enum List {
  Cons(i32.Box<List>),
  Nil,
}

// Create a list
let a = List::Cons(5.Box::new(List::Cons(10.Box::new(List::Nil))
  )
);

// create list B and join list A
let b = List::Cons(3.Box::new(a));

// create list C and join list A
let c = List::Cons(4.Box::new(a)); // The ownership of a has been moved
Copy the code

Use references to solve the problem of transferring ownership of A

We can change the list type in Box to a reference type, so that b and C can both reference A:

#[derive(Debug)]
enum List<'a> {
  Cons(i32.BoxThe < &'a List<'a>>),
  Nil,
}
Copy the code

Reference Method 1:

let a = List::Cons(5.Box::new(&List::Cons(10.Box::new(&List::Nil)) // An error was reported, creating a temporary reference &nil, but it was destroyed when a was assigned));// &nil the closing statement here is destroyed before a has been created
println!("{:? }", a);
Copy the code

Reference Mode 2:

let a2 = &List::Nil;
let a1 = &List::Cons(10.Box::new(a2));
let a = &List::Cons(5.Box::new(a1));

let b = List::Cons(3.Box::new(a));
let c = List::Cons(4.Box::new(a));
println!("{:? }", b); // Cons(3, Cons(5, Cons(10, Nil)))
println!("{:? }", c); // Cons(4, Cons(5, Cons(10, Nil)))
Copy the code

Although it can be done, nesting relationships are not very intuitive.

Use Rc<T> to resolve

Rc<T> supports multiple ownership and the Rc in its name is short for Referencecounting. For those of you who know the concept of garbage collection, the Rc<T> type maintains an internal reference-count to verify that the value is still in use. A zero number of references to a value means that the value can be safely cleaned up without triggering reference invalidation:

use std::rc::Rc; / / into the Rc
#[derive(Debug)]
enum List {
  Cons(i32, Rc<List>), // replace Box with Rc
  Nil
}

// Create an Rc instance
let a = Rc::new(List::Cons(5,
  Rc::new(List::Cons(10,
    Rc::new(List::Nil)
  ))
));

// Rc::clone is just an incremental reference count, although a. lone can also be used, but the data will be deeply copied
let b = List::Cons(3, Rc::clone(&a));

// Increase the number of references again
let c = List::Cons(4, Rc::clone(&a));

println!("{:? }", b); // Cons(3, Cons(5, Cons(10, Nil)))
println!("{:? }", c); // Cons(4, Cons(5, Cons(10, Nil)))
Copy the code

Observing reference counting

The current number of references can be obtained using the method provided by Rc<T> :

let a = Rc::new(List::Cons(5,
  Rc::new(List::Cons(10,
    Rc::new(List::Nil)
  ))
));

println!("Reference count after creation of a: {}", Rc::strong_count(&a));
// The reference count after creating a: 1

let b = List::Cons(3, Rc::clone(&a));
println!("Reference count after creation of b: {}", Rc::strong_count(&a));
// The reference count after b is created: 2

// Enter a new scope
{
	let c = List::Cons(2, Rc::clone(&a));
	println!("Reference count after c creation: {}", Rc::strong_count(&a));
  // Reference count after c is created: 3
} // when c leaves scope, the reference count is automatically reduced by 1.

println!("Reference count after c destruction: {}", Rc::strong_count(&a));
// The reference count after c is destroyed: 2
Copy the code

Rc<T> makes it possible to share read-only data between different parts of a program through immutable references. If Rc<T> also allowed to hold multiple mutable references, it would violate the borrowing rule: multiple mutable borrows pointing to the same region would result in data contention and data inconsistencies.