Example
This code focuses on lazy initialization of fields and shows how implicit dependencies work. Essentially, it forms the basis of a dependency manager or a large object requiring complex initialization logic.
A quick explanation:
- The
lazy
argument of thefxstruct
macro declares that all fields of the struct are initialized lazily by default. The exception is theorder
field, which opts out usinglazy(off)
. - The
count
field is assigned a default value of 42. Thus, a newly created instance ofFoo
will have this value from the start, but since it’s lazy it can be reset to an uninitialized state with theclear_count
method (introduced by theclearer
attribute). The next read will initialize it by callingbuild_count
. Thepredicate
argument also provides ahas_count
method to check whether the field currently holds a value. - The
comment
field is purely lazy, without a default value.
Because laziness means a field only gets its value when first accessed, all fields automatically receive accessor methods. The count
field is special here: its accessor is explicitly requested with get(copy)
, so instead of returning a reference (the usual behavior), it returns the usize
value directly, which is more ergonomic since usize
implements Copy
.
The overall point of this sample is to demonstrate how the comment
field gets its value constructed using the count
field. As soon as the count
changes, the comment
gets a new value after re-initialization.
The example also logs each call to the corresponding builder methods of count
and comment
into the order
field. By inspecting its content later, we can prove that the builders were only invoked when really needed, and that the order of invocation was determined by the initialization logic of the fields.
use fieldx::fxstruct;
#[fxstruct(lazy)]
struct Foo {
#[fieldx(get(copy), predicate, clearer, default(42))]
count: usize,
#[fieldx(predicate, clearer)]
comment: String,
#[fieldx(lazy(off), inner_mut, get, get_mut)]
order: Vec<&'static str>,
}
impl Foo {
fn build_count(&self) -> usize {
self.order_mut().push("Building count.");
12
}
fn build_comment(&self) -> String {
self.order_mut().push("Building foo.");
// If `count` isn't initialized yet it will be initialized lazily. during the call to the accessor method.
format!("foo is using count: {}", self.count())
}
}
let mut foo = Foo::new();
// No call to the accessor method has been made yet, the field remains uninitialized.
assert!(!foo.has_comment());
// The `count` field has a default value, so it is initialized.
assert!(foo.has_count());
// No builder methods have been called yet, so the order is empty.
assert!(foo.order().is_empty());
// For the first time the count is 42, the default value. The builder method for `comment` is using that.
assert_eq!(foo.comment(), "foo is using count: 42");
// Now we reset the count field to uninitialized state.
foo.clear_count();
assert!(!foo.has_count());
// `comment` is still initialized and reflects the original default value of `count`.
assert_eq!(foo.comment(), "foo is using count: 42");
// Reset `comment` to uninitialized state.
foo.clear_comment();
// Make sure it is unset.
assert!(!foo.has_comment());
// This time the `count` field will have its value from the builder method.
assert_eq!(foo.comment(), "foo is using count: 12");
// Both `comment` and `count` has values, so this call as just returns the value of `comment`.
assert_eq!(foo.comment(), "foo is using count: 12");
// Every call of `count` and `comment` builder methods are pushing their actions to the order field. At this point
// it must contain three entries:
// - one for the first call to `comment` where `count` had its default value and thus its builder wasn't involved;
// - and one for the call to `comment` after both fields was cleared where `count` was built by its builder method;
assert_eq!(foo.order().len(), 3);
assert_eq!(foo.order()[0], "Building foo.");
assert_eq!(foo.order()[1], "Building foo.");
assert_eq!(foo.order()[2], "Building count.");