Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reference Counted Structs

Another typical Rust pattern is to use Rc (or Arc) to allow multiple owners of a struct. There are various possible reasons why one might want to do this, but let us not delve into them here. The point is that sometimes we don't just need an object to be reference counted, but we need or want it to be constructed this way.

Example which we promised not to go into..

OK, let's digress into an example once. Say you need to implement a parent-child relationship between two or more structs. In this case, if there is no pressure from the performance side, the parent object can be reference counted to simplify the task of keeping a reference to it for its children.

As always, the way to achieve this pattern is as simple as adding the rc argument to the struct declaration:

#[fxstruct(rc)]

Sync/async and plain modes of operation are supported, resulting in either Arc or Rc being used as the container type respectively.

The rc argument comes with a perk but, at the same time, with a cost.

The cost is an additional implicit field being added to the struct, which holds a plain or sync Weak reference to the object itself.

The perk is two new methods in the implementation: <a name="a002"></a>myself and <a name="a003"></a>myself_downgrade. The first one returns a strong reference to the object itself, while the second one returns a weak reference. The latter is useful when you need to pass a reference to the object to some other code that may outlive the object itself, and you want to avoid keeping it alive longer than necessary.

use fieldx::fxstruct;

#[fxstruct(sync, rc, builder)]
struct LibraryInventory {
    /// Map ISBN to Book
    #[fieldx(inner_mut, get, get_mut)]
    books: HashMap<String, Book>,
}

impl LibraryInventory {
    fn make_an_inventory(&self) {
        // The inventory must be available at all times for being able to serve readers. The `books` field is
        // lock-protected. So, we take care as of not locking it for too long by utilizing this naive approach.
        let isbns = self.books().keys().cloned().collect::<Vec<_>>();

        for isbn in isbns {
            if let Some(book) = self.books_mut().get_mut(&isbn) {
                // Check whatever needs to be checked about the book
                self.check_book(book);
            }
        }
    }

    fn open_the_day(&self) {
        let myself: Arc<LibraryInventory> = self.myself().unwrap();

        let inv_task = std::thread::spawn(move || {
            myself.make_an_inventory();
        });

        self.respond_to_readers();

        inv_task.join().expect("Inventory task failed");
    }
}

Warning

Feature flag sync is required for this example to compile.

Here we use reference counting to perform a self-check in a parallel thread while continuing to serve reader requests.

Note

Since the self-reference is weak, the myself method must upgrade it first to provide us with a strong reference. Since upgrading gives us an Option, it must be unwrapped. This is safe to do in the example code because the object is alive, hence its counter is at least 1. However, within the drop() method of the Drop trait, the myself method will return None since the only possible case when the Drop trait is activated is when the reference count reaches zero.

Because the rc argument results in methods being added to the implementation, it is a helper method. Its literal string argument allows you to change the name of the myself method:

#[fxstruct(rc("my_own"))]