本指南說明 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
}
}
現在,在主要程式 (或單元測試中) 建構獨木舟,並附加至檢查樹狀結構:
// 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
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
以外,所有欄位都必須為 &mut T
或 &T
實作 Inspect
。
如果有 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
擴充功能特徵方法時,應該在執行個體化後立即附加一次檢查類型:
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
呼叫。而是自動以 Stable
的子項附加 Yak
。不過,如果 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
會經歷與直接使用純檢查屬性相同的結構。那麼,為什麼要使用 IValue
?如果您只需要寫入或增加值,您可以使用純檢查屬性。如果也需要讀取值,則應使用 IValue
。
IDebug<T>
IDebug<T>
智慧型指標會納入可進行偵錯的類型,並將 T
的偵錯表示法維持為 StringProperty
。這對於:
- 外國類型無法加入檢查實作
- 偵錯,快速驗證程式的某些狀態
請避免在正式版程式碼中使用偵錯表示法,因為這類表示法有下列問題:
- 偵錯表示法會在每次檢查更新時寫入,這可能會導致不必要的效能負擔。
- 偵錯表示法可能會耗盡檢查 VMO 的空間,導致整個檢查狀態遭到截斷。
- 偵錯表示法無法與隱私管道整合:如果偵錯字串中包含任何 PII,則整個欄位都必須視為 PII。您可以管理自己的結構化資料,藉此精細遮蓋含有 PII 的欄位。
Unit
特徵
Unit
特徵會說明類型的檢查表示法、如何初始化及更新類型。應實作做為邏輯分葉節點的類型,且「不支援」個別欄位更新。這個程式庫提供大多數原始的 Unit
實作。舉例來說,u8
、u16
、u32
和 u64
都會以 UintProperty
表示。
IValue 中的用量
Unit
類型應納入 IValue<T: Unit>
(請見上文),以取得 RAII 管理的可檢查類型。不建議直接呼叫 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")
:請改用其他名稱。根據預設,系統會使用欄位名稱。