Box, Rc, Arc, and the relation with threads.

This is a learning journal, so content here is compressed with the main points, mainly.

The Threads

Working with threads in traditional programming languages requires some cares about the shared memory. A problem can occur when one or more threads changes a shared data at the same time, raising a racing error.

Rust works in a called thread-safe mode, it does not share data between the threads and just allow thread-safe operations. It seems something like ownership strategy that prevent memory errors also makes possible to write safe concurrent programs.

Before dive into the main point of the Blog, some topics needs to be overviewed to construct a progressive understatement of the content.

What is Box<T>?

Box is, basically, a non-copyable pointer's type with a defined size in the stack to a type generic T in the heap. A Box can be used to:

  • When is needing to know the type' size in compile time
  • When is required to transfer ownership of a large amount of data, and it can't be copied
  • When must know whether a type implements a particular trait.
  • And so on...

The Rust's compiler needs to know the size of the things at compile time, so to define a struct with a variable size can be a problem for the compiler. To solve this, it is necessary to wrap the problematic type in a Box, that has a fixed type in Stack, it will point to a heap, in so can grow freely.

The Stack in Rust has a limit of memory of 2MB, and is settled at compile time.

In this example, the block encapsulates other blocks within itself, making the definition of total blockchain size impossible to do. This is for to the fact of the blockchains can contains 'n' blocks.

enum Blockchain {
    Block(u32, Box<Blockchain>),
    End
}

use Blockchain::{Block, End};

fn main() {
    let blockchain = Block(0, 
            Box::new(Block(1,
            Box::new(Block(2, 
            Box::new(End))))));
}

What is Rc<T>?

Rc stands for reference counting, and it is used in Rust to makes possibles a single values has multiples owners. The behavior of Rc is, as the name says, keeps track of the number of references to a value, which determines whether a value is still in use. In case the references number is zero, the value can be freely.

It is important to say that Rc is "single thread"

This example is similar to the other, however at this one, the main blockchain transfer its ownership to others.

use std::rc::Rc;

enum Blockchain {
    Block(u32, Rc<Blockchain>),
    End
}

use Blockchain::{Block, End};

fn main() {
    let blockchain_main = Rc::new(Block(0, Rc::new(Block(1, Rc::new(Block(2, Rc::new(End)))))));

    let blockchain_a = Block(10, Rc::clone(&blockchain_main));
    let blockchain_b = Block(100, Rc::clone(&blockchain_main));
}

What is Arc<T>?

Arc is a type from the std::sync submodule, and with it is possible to handle shared mutable state through threads. Arc stands for Atomic Reference Counting, it is like the Rc, Reference Counting, previously saw, but with the possibility to work with threads.

Thanks for @amiguxo for give me a hand to understand this.

The following example gets the blockchain created in main function and clone it to each thread spawned, after that, each one create itself blockchain based on the main one.

use std::thread;
use std::sync::Arc;
enum Blockchain {
    Block(u32, Arc<Blockchain>),
    End
}

use Blockchain::{Block, End};

fn main() {
    let blockchain = Arc::new(
        Block(0, 
            Arc::new(Block(1,
            Arc::new(Block(2,
            Arc::new(End)))))
    ));

    for t in 3..10 {
        let clone = blockchain.clone();
        thread::spawn(move || {
            let _blockchain_thread = Block(t, clone);
        }).join().unwrap();
    }
}

As a said in the beginner, this is my journal of learning about this topic, so some information can be too compressed, but I think it can be useful to someone.

Thanks for reading and feel free to share, comment or correct me.

No Comments Yet