This guide covers the usage of the fuchsia_inspect_derive
library, and assumes that you are familiar with Inspect and have basic experience with the fuchsia_inspect
library.
The fuchsia_inspect_derive
library provides ergonomic macros, traits and smart pointers around the fuchsia_inspect
library, that makes it easier to integrate inspect with your Rust code base, by:
fuchsia_inspect
directly.IDebug<T>
for usage and constraints.At the same time, it preserves the performance and semantics of a manual inspect integration, by:
When you integrate your Rust code base with this library, be aware that:
fuchsia_inspect
directly.Inspect
manually:Option<T>
and other enums.This section shows an example where you take an existing data structure and apply inspect to that structure. Let's start with a simple example, a Yak:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="quick_start_before_decl" adjust_indentation="auto" %}
Then, consider this construction site:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="quick_start_before_init" adjust_indentation="auto" %}
Let's make the yak inspectable. In particular:
Now use fuchsia_inspect_derive
to make this Yak inspectable:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="quick_start_after_decl" adjust_indentation="auto" %}
Now, in your main program (or in a unit test), construct the yak and attach it to the inspect tree:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="quick_start_after_init" adjust_indentation="auto" %}
Now you have integrated a simple program with Inspect. The rest of this guide describes the types, traits and macros of this library, and how to apply them to real world programs.
Inspect
derive(Inspect)
can be added to any named struct, but each of its fields must also implement Inspect
(except for inspect_node
and skipped fields). The library provides implementations of Inspect
for several types:
IOwned
smart pointersUintProperty
, StringProperty
, etc) except for arrays and histogramsInspect
types. See the section on nesting.If you add a type that isn't Inspect
, you get a compiler error:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/compiler_errors.rs" region_tag="derive_inspect_unwrapped" adjust_indentation="auto" %}
Inspect
TypesInspect
types can be freely nested, like so:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_nested_decl" adjust_indentation="auto" %}
All fields, except for skipped fields and inspect_node
, must implement Inspect
, either for &mut T
or &T
.
If an inspect_node
field is present, instances will have its own node in the inspect tree. It must be a fuchsia_inspect::Node
:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_node_present_decl" adjust_indentation="auto" %}
If inspect_node
is absent, fields will be attached directly to the parent node (meaning that the name provided to with_inspect
will be ignored):
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_node_absent_decl" adjust_indentation="auto" %}
If your type needs to add or remove nodes or properties dynamically, it should own an inspect node. The inspect node is needed when nodes or properties are added or removed after the initial attachment.
derive(Inspect)
supports the following field attributes:
inspect(skip)
: The field is ignored by inspect.inspect(rename = "foo")
: Use a different name. By default, the field name is used.inspect(forward)
: Forwards the attachment to an inner Inspect
type, omitting one layer of nesting from the inspect hierarchy. All other fields should not have any inspect attributes. The type must NOT have an inspect_node
field. Useful for wrapper types. For example:{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_forward_decl" adjust_indentation="auto" %}
If you are integrating with a code base that uses fuchsia_inspect
directly, its types are not be aware of fuchsia_inspect_derive
. Do not add such manually managed types as fields to an Inspect
type directly. Instead, implement Inspect
manually for the type. Avoid attaching manually outside of the Inspect
trait, since attachment in fuchsia_inspect_derive
occurs after construction. Attaching in a constructor can silently cause its inspect state to be absent.
An inspect type should be attached once, and immediately after instantiation, using the with_inspect
extension trait method:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_node_present_init" adjust_indentation="auto" %}
If you have a nested Inspect
structure, you should only attach the top-level type. The nested types are attached implicitly:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="inspect_nested_init" adjust_indentation="auto" %}
Note that when a Yak
is constructed from within a Stable
, there is no with_inspect
call present. Instead, the Yak
is automatically attached as a child of the Stable
. However, you can still attach a Yak
when it is the top-level type, such as in the unit tests for Yak
. This allows you to test any Inspect
type in isolation.
You can optionally choose to supply inspect nodes in constructors instead of explicitly calling with_inspect
at the construction sites. First, ensure that the type is NOT nested under another Inspect
type (as this would cause duplicate attachments). Sedondly, make sure to document this fact clearly, so the calling user is aware of your attachment convention.
In Rust (and particularly async
Rust), it is common to use interior mutability. This library provides Inspect
implementations for several smart pointers and locks:
std
: Box
, Arc
, Rc
, RefCell
, Mutex
and RwLock
Cell
does NOT work. Instead, upgrade to a RefCell
.parking_lot
: Mutex
and RwLock
futures
: Mutex
Generally, interior mutability within a derive(Inspect)
type just works:
#[derive(Inspect)] struct Stable { - yak: Yak, + yak: Arc<Mutex<Yak>>, - horse: Horse, + horse: RefCell<Horse>, inspect_node: fuchsia_inspect::Node, }
Make sure to put your smart pointers inside your mutability wrapper:
struct Yak { - coins: IValue<Rc<RwLock<u32>>>, // Won't compile + coins: Rc<RwLock<IValue<u32>>>, // Correct }
If an inner type is behind a lock, attachment will fail if the lock is acquired by someone else. Hence, always attach immediately after instantiation.
Inspect
ManuallyThe derive(Inspect)
derive-macro generates an impl Inspect for &mut T { .. }
. Oftentimes, this works fine, but in some cases you may need to implement Inspect
manually. Fortunately, the Inspect
trait is quite simple:
trait Inspect { /// Attach self to the inspect tree fn iattach(self, parent: &Node, name: AsRef<str>) -> Result<(), AttachError>; }
Do not return an AttachError
for structural errors in the data. Instead, report the error using logs or an inspect node. AttachError
is reserved for irrecoverable invariant errors that fail the entire attachment.
IOwned
Smart PointersSmart pointers may sound scary, but you probably use them everyday already. For instance, Arc
and Box
are smart pointers. They are statically dispatched, and have first-class support in Rust (through deref coercion). This makes them minimally invasive.
fuchsia_inspect_derive
comes with a few useful smart pointers that implement Inspect
and can be used to wrap primitives, debuggable types, and more. They all share the same behavior: An IOwned<T>
smart pointer owns a generic source type T
and some associated inspect data.
Here is a demonstration of the IOwned
API:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="smart_pointers_ivalue" adjust_indentation="auto" %}
An IOwned<T>
smart pointer should not be instantiated directly, but rather one of its variants:
IValue<T>
The IValue<T>
smart pointer wraps a primitive (or any type T: Unit
). For example, an IValue<f32>
is represented as a DoubleProperty
, and an IValue<i16>
is represented as an IntProperty
.
An IValue
of a primitive results in the same structure as using a plain inspect property directly. So, why would you use an IValue
? If you only need to write or increment a value, you can use a plain inspect property. If you also need to read the value, you should use an IValue
.
IDebug<T>
The IDebug<T>
smart pointer wraps a debuggable type, and maintains the debug representation of T
as a StringProperty
. This is useful for:
Avoid using debug representations in production code, since they come with the following issues:
Unit
TraitThe Unit
trait describes the inspect representation of a type, how to initialize it, and how to update it. It should be implemented for types that act as a logical leaf node, and does NOT support per-field updates. This library provides implementations of Unit
for most primitives. For example, u8
, u16
, u32
and u64
are represented as a UintProperty
.
A Unit
type should be wrapped in an IValue<T: Unit>
(see above), for a RAII managed inspectable type. It is NOT recommended to call methods on Unit
directly.
Unit
Sometimes a logical Unit
is a composite type. Unit can be derived for a named struct, as long as its fields also implement Unit
. For example:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="unit_plain_decl" adjust_indentation="auto" %}
Unit
can be nested, but keep in mind that all fields are still written at the same time:
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/diagnostics/inspect/rust-ergonomic/src/main.rs" region_tag="unit_nested_decl" adjust_indentation="auto" %}
derive(Unit)
supports the following field attributes:
inspect(skip)
: The field is ignored by inspect.inspect(rename = "foo")
: Use a different name. By default, the field name is used.