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

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 the fxstruct macro declares that all fields of the struct are initialized lazily by default. The exception is the order field, which opts out using lazy(off).
  • The count field is assigned a default value of 42. Thus, a newly created instance of Foo will have this value from the start, but since it’s lazy it can be reset to an uninitialized state with the clear_count method (introduced by the clearer attribute). The next read will initialize it by calling build_count. The predicate argument also provides a has_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.");