本指南介绍了
fuchsia_inspect_derive
库,并假设您熟悉
检查
基本熟悉
fuchsia_inspect
库。
概览
fuchsia_inspect_derive
库提供工效学宏、特征和
围绕 fuchsia_inspect
库设置了智能指针,以便更轻松地
将检查器与 Rust 代码库集成,具体方法如下:
- 拥有源数据和检查同一 RAII 类型下的数据
- 符合语言习惯。对基元、通用内部的一流支持 可变性模式和异步。
- 生成重复的样板代码
- 提供一种统一的方式来附加要检查的类型
- 支持逐步与现有代码库集成,
尚不支持检查,而与
fuchsia_inspect
。 - 支持缺少检查集成的外来类型。请参阅
IDebug<T>
表示用法和限制条件。
同时,它保留了手动检查的性能和语义 集成,具体方式如下:
- 提交精细的检查树修改,其中逻辑叶节点是 独立更新
- 仅应用静态调度,以避免额外的运行时开销。
- 不使用任何其他同步基元。
注意事项
将 Rust 代码库与此库集成时,请注意:
- 该库镜像 Rust 程序的内部类型层次结构。受限
结构修改(例如重命名、展平和省略字段)
(类似于 Serde)。如果所需的检查树
结构与类型层次结构截然不同,您应该考虑
直接使用
fuchsia_inspect
。 - 某些功能尚不受支持,您需要
手动实现
Inspect
:- 延迟节点、直方图和检查数组。
Option<T>
和其他枚举。- 集合类型,例如矢量和地图。
- StringReferences
- 该库会提升自定义智能指针,这会创建另一个 数据封装层。
快速入门
本部分举例说明,您将采用现有数据结构并 将检查应用于该结构。我们先来看一个简单的 Yak 示例:
struct Yak {
// TODO: Overflow risk at high altitudes?
hair_length: u16, // Current hair length in mm
credit_card_no: String, // Super secret
}
impl Yak {
pub fn new() -> Self {
Self { hair_length: 5, credit_card_no: "<secret>".to_string() }
}
pub fn shave(&mut self) {
self.hair_length = 0;
}
}
然后,以下面的建筑工地为例:
let mut yak = Yak::new();
yak.shave();
让我们来检测麋鹿。具体而言:
- 暴露当前头发长度
- 展示美洲蝾螈的剃须次数
- 请勿泄露信用卡号
现在,使用 fuchsia_inspect_derive
使此 Yak 变得可检查:
use fuchsia_inspect_derive::{
IValue, // A RAII smart pointer that can be attached to inspect
Inspect, // The core trait and derive-macro
WithInspect, // Provides `.with_inspect(..)`
};
#[derive(Inspect)]
struct Yak {
#[inspect(rename = "hair_length_mm")] // Clarify that it's millimeters
hair_length: IValue<u16>, // Encapsulate primitive in IValue
#[inspect(skip)] // Credit card number should NOT be exposed
credit_card_no: String,
shaved_counter: fuchsia_inspect::UintProperty, // Write-only counter
inspect_node: fuchsia_inspect::Node, // Inspect node of this Yak, optional
}
impl Yak {
pub fn new() -> Self {
Self {
hair_length: IValue::new(5), // Or if you prefer, `5.into()`
credit_card_no: "<secret>".to_string(),
// Inspect nodes and properties should be default-initialized
shaved_counter: fuchsia_inspect::UintProperty::default(),
inspect_node: fuchsia_inspect::Node::default(),
}
}
pub fn shave(&mut self) {
self.hair_length.iset(0); // Set the source value AND update the inspect property
self.shaved_counter.add(1u64); // Increment counter
}
}
现在,在您的主程序(或单元测试)中,构建 yak 并将其连接 添加到检查树:
// Initialization
let mut yak = Yak::new()
.with_inspect(/* parent node */ inspector.root(), /* name */ "my_yak")?;
assert_data_tree!(inspector, root: {
my_yak: { hair_length_mm: 5u64, shaved_counter: 0u64 }
});
// Mutation
yak.shave();
assert_data_tree!(inspector, root: {
my_yak: { hair_length_mm: 0u64, shaved_counter: 1u64 }
});
// Destruction
std::mem::drop(yak);
assert_data_tree!(inspector, root: {});
现在,您已将一个简单的程序与 Inspect 集成。本指南的其余部分 介绍了此库的类型、trait 和宏,以及如何将其应用于 和实际程序。
派生 Inspect
derive(Inspect)
可添加到任何命名结构体中,但其各个字段
还必须实现 Inspect
(inspect_node
和跳过的字段除外)。
该库为以下几种类型提供了 Inspect
的实现:
IOwned
智能指针- 许多常见的内部可变性封装容器
- 除以下项之外的所有检查属性(
UintProperty
、StringProperty
等) 数组和直方图 - 其他
Inspect
类型。请参阅有关嵌套的部分。
如果您添加的类型不是 Inspect
,则会收到编译器错误:
#[derive(Inspect)]
struct Yakling {
name: String, // Forgot to wrap, should be `name: IValue<String>`
}
// error[E0599]: no method named `iattach` found for struct
// `std::string::String` in the current scope
嵌套的 Inspect
类型
Inspect
类型可以自由嵌套,如下所示:
// Stable is represented as a node with two child nodes `yak` and `horse`
#[derive(Inspect)]
struct Stable {
yak: Yak, // Yak derives Inspect
horse: Horse, // Horse implements Inspect manually
inspect_node: fuchsia_inspect::Node,
}
字段和属性
除跳过字段和 inspect_node
之外的所有字段都必须实现
Inspect
,表示 &mut T
或 &T
。
如果存在 inspect_node
字段,则实例在
检查树。它必须是 fuchsia_inspect::Node
:
#[derive(Inspect)]
struct Yak {
name: IValue<String>,
age: IValue<u16>,
inspect_node: fuchsia_inspect::Node, // NOTE: Node is present
}
// Yak is represented as a node with `name` and `age` properties.
如果 inspect_node
不存在,字段将直接附加到父节点
(也就是说,提供给 with_inspect
的名称将被忽略):
#[derive(Inspect)]
struct YakName {
title: IValue<String>, // E.g. "Lil"
full_name: IValue<String>, // E.g. "Sebastian"
// NOTE: Node is absent
}
// YakName has no separate node. Instead, the `title` and `full_name`
// properties are attached directly to the parent node.
如果您的类型需要动态添加或移除节点或属性, 它应该有一个检查节点。在下列情况下,需要使用检查节点 在初始连接后添加或移除节点或属性。
derive(Inspect)
支持以下字段属性:
inspect(skip)
:检查会忽略该字段。inspect(rename = "foo")
:请使用其他名称。默认情况下,字段名称 。inspect(forward)
:将附件转发到内部Inspect
类型,省略一个 嵌套层。所有其他字段不应进行任何检查 属性。该类型不得包含inspect_node
字段。对于封装容器类型非常有用。 例如:
#[derive(Inspect)]
struct Wrapper {
// key is not included, because inspect has been forwarded.
_key: String,
#[inspect(forward)]
inner: RefCell<Inner>,
}
#[derive(Inspect)]
struct Inner {
name: IValue<String>,
age: IValue<u16>,
inspect_node: fuchsia_inspect::Node,
}
// Wrapper is represented as a node with `name` and `age` properties.
手动管理的检查类型
如果要与直接使用 fuchsia_inspect
的代码库集成,
其类型不知道 fuchsia_inspect_derive
。请勿添加此类
手动管理的类型作为字段直接添加到 Inspect
类型。相反,
针对该类型手动实现 Inspect
。
避免在 Inspect
特征之外手动附加
因为 fuchsia_inspect_derive
中的连接发生在构建之后。
附加到构造函数中可以静默地使其检查状态变为
缺失。
附加到检查树
检查类型应该附加一次,在实例化后立即附加一次,
使用 with_inspect
扩展 trait 方法:
let yak = Yak::new().with_inspect(inspector.root(), "my_yak")?;
assert_data_tree!(inspector, root: { my_yak: { name: "Lil Sebastian", age: 3u64 }});
如果您有嵌套的 Inspect
结构,则应仅附加顶层
类型。嵌套类型是隐式附加的:
// Stable owns a Yak, which also implements Inspect.
let stable = Stable::new().with_inspect(inspector.root(), "stable")?;
assert_data_tree!(inspector,
root: { stable: { yak: { name: "Lil Sebastian", age: 3u64 }}});
请注意,从 Stable
中构建 Yak
时,
有 with_inspect
个通话。相反,Yak
会作为
Stable
的子级。不过,当 Yak
是
顶级类型,例如在 Yak
的单元测试中。这样你就可以测试
Inspect
类型进行隔离。
您可以选择在构造函数中提供检查节点,而不是
在建筑工地明确调用 with_inspect
。首先,请确保
该类型未嵌套在另一个 Inspect
类型下(因为这会导致
重复附件)。诚然,请务必清楚地记录这一事实
这样,调用用户就能知道您的附件惯例。
内部可变性
在 Rust(尤其是 async
Rust)中,通常使用内部函数,
可变性。此库为多个智能版本提供 Inspect
实现,
指针和锁:
std
:Box
、Arc
、Rc
、RefCell
、Mutex
和RwLock
- 请注意,
Cell
不起作用。请改为升级到RefCell
。
- 请注意,
parking_lot
:Mutex
和RwLock
futures
:Mutex
通常,derive(Inspect)
类型中的内部可变性非常实用:
#[derive(Inspect)]
struct Stable {
- yak: Yak,
+ yak: Arc<Mutex<Yak>>,
- horse: Horse,
+ horse: RefCell<Horse>,
inspect_node: fuchsia_inspect::Node,
}
请务必将智能指针放在可变性封装容器中:
struct Yak {
- coins: IValue<Rc<RwLock<u32>>>, // Won't compile
+ coins: Rc<RwLock<IValue<u32>>>, // Correct
}
如果内部类型是在锁后面,那么在锁定处于 被他人获取。因此,请始终将 实例化。
手动实现 Inspect
derive(Inspect)
派生宏会生成
impl Inspect for &mut T { .. }
。这通常效果不错
在某些情况下,您可能需要手动实现 Inspect
。幸运的是
Inspect
特征非常简单:
trait Inspect {
/// Attach self to the inspect tree
fn iattach(self, parent: &Node, name: AsRef<str>) -> Result<(), AttachError>;
}
不对数据中存在结构错误返回 AttachError
。
请改用日志或检查节点来报告错误。
AttachError
预留用于符合以下条件的不可恢复的不变错误:
导致整个连接失败。
IOwned
智能指针
智能指针听起来可能很吓人,但您可能已经每天都使用了。对于
实例,Arc
和 Box
是智能指针。它们以静态方式分派
并在 Rust 中提供一流的支持(通过 deref 强制转换)。这使得它们
侵入性极小。
fuchsia_inspect_derive
附带一些实用的智能指针,这些指针可实现
Inspect
,可用于封装基元、可调试类型等。他们
它们的行为方式相同:IOwned<T>
智能指针拥有一个通用
来源类型 T
和一些关联的检查数据。
以下是 IOwned
API 的演示:
let mut number = IValue::new(1337u16) // IValue is an IOwned smart pointer
.with_inspect(inspector.root(), "my_number")?; // Attach to inspect tree
// Dereference the value behind the IValue, without mutating
assert_eq!(*number, 1337u16);
{
// Mutate value behind an IOwned smart pointer, using a scope guard
let mut number_guard = number.as_mut();
*number_guard = 1338;
*number_guard += 1;
// Inspect state not yet updated
assert_data_tree!(inspector, root: { my_number: 1337u64 });
}
// When the guard goes out of scope, the inspect state is updated
assert_data_tree!(inspector, root: { my_number: 1339u64 });
number.iset(1340); // Sets the source value AND updates inspect
assert_data_tree!(inspector, root: { my_number: 1340u64 });
let inner = number.into_inner(); // Detaches from inspect tree...
assert_eq!(inner, 1340u16); // ...and returns the inner value.
IOwned<T>
智能指针不应直接进行实例化,而应直接实例化
变体:
IValue<T>
IValue<T>
智能指针封装了一个基元(或任何类型 T: Unit
)。
例如,IValue<f32>
表示为 DoubleProperty
,
IValue<i16>
表示为 IntProperty
。
基元的 IValue
生成的结构与使用 plain
直接检查属性。那么,为什么要使用 IValue
?如果您仅
需要写入或递增值,则可以使用普通检查属性。如果您
同样需要读取该值,则应使用 IValue
。
IDebug<T>
IDebug<T>
智能指针会封装可调试类型,并维护调试
以 StringProperty
表示的 T
的表示形式。这对于以下情况非常有用:
- 外部类型(无法添加检查实现)
- 调试,用于快速验证程序的某些状态
避免在正式版代码中使用调试表示法,因为它们 以下问题:
- 每次检查更新时都会编写调试表示法,这可能会导致 不必要的性能开销
- 调试表示法可能会占用检查 VMO 的空间,导致 截断整个检查状态。
- 调试表示法不能与隐私流水线集成(如果有) PII 作为调试字符串的一部分公开,因此整个字段必须 个人身份信息管理您自己的结构化数据后,您可以精细地隐去数据 包含个人身份信息的字段。
Unit
特征
Unit
trait 描述了类型的检查表示法,以及如何
以及如何对其进行更新。它应该实现
作为逻辑叶节点,并且不支持按字段更新。此库
为大多数基元提供了 Unit
的实现。例如,u8
。
u16
、u32
和 u64
表示为 UintProperty
。
IValue 的使用
对于 RAII,Unit
类型应封装在 IValue<T: Unit>
(参见上文)中
托管式可检查类型不建议对 Unit
调用方法
。
派生 Unit
有时,逻辑 Unit
是一种复合类型。可以针对命名的
结构体,只要其字段也实现 Unit
即可。例如:
// Represented as a Node with two properties, `x` and `y`, of type UintProperty
#[derive(Unit)]
struct Point {
x: f32,
y: f32,
}
Unit
可以嵌套,但请注意,所有字段仍会写入
:
// Represented as a Node with two child nodes `top_left` and `bottom_right`
#[derive(Unit)]
struct Rect {
#[inspect(rename = "top_left")]
tl: Point,
#[inspect(rename = "bottom_right")]
br: Point,
}
属性
derive(Unit)
支持以下字段属性:
inspect(skip)
:检查会忽略该字段。inspect(rename = "foo")
:请使用其他名称。默认情况下,字段名称 。