Modes Of Operation
There is no concise way to explain how a mode of operation works for every specific case in FieldX. Up to some limited extent, one could say that this entire book is dedicated to exploring this topic. However, here are a few points to help you get started.
Declaration
The arguments that set one of the modes are either named after the modes themselves: plain
, sync
, or r#async
, where the r#
is required to allow use of the async
keyword as an argument name; or these keywords can be used as sub-arguments of the mode
argument: mode(async)
. In the latter case, the r#
prefix before async
is not required.
Both options are entirely equivalent; choose the one that is more readable for your use case.
Plain
The plain mode is the default mode of operation for FieldX. It does not provide any guarantees regarding thread safety or concurrency.
Sync
Suppose you have a struct Foo
with some fields that are not Sync
or Send
. If the struct is annotated with the #[fxstruct]
attribute, we say it operates in plain mode. At some point, you may realize that you need to use Foo
in a concurrent context. Often, it may be enough to add the sync
argument to the #[fxstruct]
attribute, like this:
#[fxstruct(sync)]
struct Foo {
// ...
}
Now, Foo
operates in sync mode and implements the Sync+Send
traits. The specific meaning of adding the sync
argument may vary for each individual field of Foo
, depending on their declarations. Let's consider one example to illustrate this:
#[fxstruct(get)]
struct Foo {
#[fieldx(inner_mut, set)]
value: String,
}
Here, the value
field is a plain one. The inner_mut
attribute enables the implementation of the inner mutability pattern for the field. Technically, this means the field type is now wrapped in RefCell
, which is Send
but not Sync
. So, let's add the argument:
#[fxstruct(sync, get)]
struct Foo {
#[fieldx(inner_mut, set)]
value: String,
}
Now FieldX will use RwLock
instead of RefCell
1. In an ideal world, not only would we not need to change any of the foo.set(new_value)
calls, but as long as all our accessor calls are dereferenced, their usage will remain the same. Moreover, if for some reason we always need a clone of the value
field, then with the following declaration, all uses of the value
accessor will remain the same without any caveats:
#[fxstruct(sync)]
struct Foo {
#[fieldx(inner_mut, get(clone), set)]
value: String,
}
Async
The async mode is covered in a later chapter.
Struct or Field?
Since FieldX is primarily a field-oriented crate, it allows manipulation of operation modes for individual fields. For example, with the Foo
struct from the examples above, you could do the following:
#[fxstruct]
struct Foo {
#[fieldx(inner_mut, sync, set)]
value: String,
// Or `r#async` because keywords can't be used as first-level argument names.
#[fieldx(lock, mode(async))]
async_value: String,
}
-
The
inner_mut
andlock
arguments are aliases for the same functionality insync
andasync
modes. Thelock
argument exists for readability and convenience, as marking a field as locked automatically implies that it issync
. ↩