Async
The async mode of operation is identical to the sync mode in many respects. They even share the same code generator behind the scenes. Yet, there are details that warrant additional attention.
The primary use of async is with lazily initialized fields and locks. Let's have a look at why this is the case.
Locks
An early FieldX design didn't provide async support at all. While for lazies it was a nuisance, for locks it was considered acceptable only until the author was hard bitten by a lock that actually blocked a tokio task thread, effectively freezing all tasks running on the thread—whereas one of the tasks was supposed to actually release the lock. You got it!
That's all there is to it. With the use of async locks, the above situation wouldn't happen (in that particular case); or, at least, deadlocks would have a less severe impact on the system.
Lazies
Lazy field initialization is another driving force behind the introduction of the async mode. Consider initializing a network resource in an async context, for example. Either this brings us back to the boilerplate of manually implementing laziness, or to the boilerplate of creating a new runtime only to use the async code in a builder method! A choice without a choice...
Let's have a look at what we can do rather than reflecting on what we wouldn't:
#[fxstruct(get)]
struct RegistryRecord {
location: String,
#[fieldx(mode(async), lock, get(copy), get_mut)]
available: u32,
#[fieldx(optional, get(as_ref))]
signed_by: String,
}
#[fxstruct(get, builder(into))]
struct Book {
title: String,
author: String,
#[fieldx(get(copy), builder(into(off)))]
year: u32,
#[fieldx(get(copy), builder(into(off)))]
bar_code: u32,
#[fieldx(r#async, lazy)]
registry_record: RegistryRecord,
}
impl Book {
async fn build_registry_record(&self) -> RegistryRecord {
self.request_registry_record(self.bar_code).await
}
}
let book = Book::builder()
.title("The Catcher in the Rye")
.author("J.D. Salinger")
.year(1951)
.bar_code(123456)
.build()
.expect("Failed to create Book object");
let registry_record = book.registry_record().await;
*registry_record.available_mut().await -= 1;
As usual, nothing too complicated here. The accessor methods become async
, which is expected because they are the ones that call the builder method.
Backend Choice
FieldX provides two options for the async backend: tokio
and async-lock
. These backends are utilized for their implementations of RwLock
and OnceCell
. The selection is controlled via the async-tokio
or async-lock
feature flags, with async-tokio
being the default.