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

Locks

Support for sync/async modes of operation is incomplete without providing a way to lock-protect data. FieldX can do this for you at the field level by using the lock argument.

Note

Using inner_mut with explicit sync or mode(sync) has the same effect as using the lock argument.

Note

The async mode still requires the async argument to be used.

For greater flexibility, FieldX utilizes read-write locks provided by the parking_lot crate for the sync mode, and either tokio or async-lock for the async mode, depending on which feature flag is enabled. This design decision aligns well with the immutable and mutable accessor patterns.

Making a field lockable affects the return values of its accessors. Without focusing on specific sync or async modes, the immutable accessor of such a field returns an RwLockReadGuard, while the mutable accessor returns an RwLockWriteGuard:

    #[rustfmt::skip]
#[fxstruct(get, builder(into))]
struct Book {
    title:     String,
    author:    String,
    #[fieldx(get(copy), builder(into(off)))]
    year:      u32,
    #[fieldx(optional)]
    signed_by: String,
    location:  String,
    #[fieldx(reader, writer, get(copy), get_mut, builder(into(off)))]
    available: u32,
    // Map borrower IDs to the time they borrowed the book.
    #[fieldx(lock, get_mut, builder(off))]
    borrowers: HashMap<String, Instant>,
}

let book = Book::builder()
    .title("The Catcher in the Rye")
    .author("J.D. Salinger")
    .year(1951)
    .signed_by("S.K.")
    .location("R42.S1".to_string()) // Row 12, Section 2
    .available(50)
    .build()
    .expect("Failed to create Book object");

let borrowers = 30;
let barrier = std::sync::Barrier::new(borrowers + 1);

thread::scope(|s| {
    for i in 0..borrowers {
        let book_ref = &book;
        let idx = i;
        let barrier = &barrier;
        s.spawn(move || {
            // Try to ensure real concurrency by making all threads start at the same time.
            barrier.wait();
            *book_ref.write_available() -= 1;
            book_ref.borrowers_mut().insert(format!("user{idx}"), Instant::now());
        });
    }

    barrier.wait();
});

assert_eq!(book.available(), 20);
assert_eq!(*book.read_available(), 20);
assert_eq!(book.borrowers().len(), borrowers as usize);

The use of the struct and its locked fields is straightforward in the example above. What is really worth mentioning are the following two points:

  1. Implicit sync mode when the lock argument is used. This means there is no need to specify sync or mode(sync) per field if it is already locked.
  2. The lock argument itself can be implicit when reader or writer arguments are used. Apparently, the sync mode is also implied in this case.

Readers And Writers

The reader and writer arguments are additional helpers that introduce methods always returning an RwLockReadGuard and RwLockWriteGuard, respectively. The methods are named after the field they are applied to, with the read_ and write_ prefixes added.

At this point, an immediate question arises: what makes them different from the immutable and mutable accessors? For the reader, the answer is the word "always" in the previous paragraph. Consider the copy subargument of the available field's get – with it, the locking happens inside the accessor, and we only get a u32 value.

For the writer, the situation is different and slightly more intricate. It will be discussed in the Lock'n'Lazy chapter.