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

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 RefCell1. 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,
}

  1. The inner_mut and lock arguments are aliases for the same functionality in sync and async modes. The lock argument exists for readability and convenience, as marking a field as locked automatically implies that it is sync.