本文件列舉 Netstack 團隊發現的模式:
- 更靈活地在遠處變更行為。
- 因為程式碼的「工作記憶體」較少,較容易審查。
這些是 Rust API 評分量表的附加內容。
這裡建議的模式提供集中式指引和知識。 所有類型的貢獻,例如編輯、新增、移除等
在編寫程式碼或審查提議下進行程式碼時,遇到一些邊緣 符合相關規範,才能找到本文件 視需要編寫替代模式
避免未使用的結果
這大多是以機器強制執行,自 https://fxrev.dev/510442 起透過 rustc 的 unused-results Lint。詳情請參閱 說明文件 。
捨棄結果時,對已忽略欄位的類型進行編碼,使其成為部分結果 合約
捨棄結果時,「偏好」使用前置字串為 _
的已命名變數
當語意意義無法立即從類型中清除,
會影響 drop
語意
系統會提示作者和評論者參考這些新增的資訊:
- 是否應使用傳回值檢查變體?
- 這個函式是否應傳回值?
注意:團隊過去在作業上使用
let () =
形式為 指出沒有捨棄任何資訊的聲明。這個練習還在 不過,導入 Lint 時不再需要採用。
範例
使用提示
// Bad. The dropped type is unknown without knowing the signature of write.
let _ = writer.write(payload);
let _n = writer.write(payload);
// Okay. The dropped type is locally visible and enforced. Absence of invariant
// enforcement such as a check for partial writes is obvious and can be flagged
// in code review.
let _: usize = writer.write(payload);
let _n: usize = writer.write(payload);
// Good.
let n = writer.write(payload);
assert_eq!(n, payload.len());
採用捨棄的格式
// Encode the type of the ignored return value.
let _: Foo = foo::do_work(foo, bar, baz);
// When destructuring tuples, type the fields not in use.
let (foo, _) : (_, Bar) = foo::foo_and_bar();
// When destructuring named structs, no annotations are necessary, the field's
// type is encoded by the struct.
let Foo{ fi, fo: _ } = foo::do_work(foo, bar, baz);
// Encode the most specific type possible.
let _: Option<_> = foo.maybe_get_trait_impl(); // Can't name opaque type.
let _: Option<usize> = foo.maybe_get_len(); // Can name concrete type.
// Use best judgement for unwieldy concrete types.
// If the type expands to formatted code that would span >= 5 lines of type
// annotation, use best judgement to cut off the signature.
let _: futures::future::Ready<Result<Buffer<_>>, anyhow::Error> = y();
// Prefer to specify if only a couple of levels of nesting and < 5 lines.
let _: Result<
Result<(bool, usize), fidl_fuchsia_net_interfaces_admin::AddressRemovalError>,
fidl::Error,
> = x();
擊敗模式
留意擊敗模式:
// Bad, this is a drop that does not encode the type.
std::mem::drop(foo());
// Prefer instead:
let _: Foo = foo();
測試
宣告
請使用要測試的內容來為測試命名,不使用 test_
前置字串。以上就是
Rust 標準程式庫中採用的模式。
如果測試名稱不足以將測試目標編碼,請新增 函式的簡短非文件註解,或置於函式主體上方 以及這個測試的運動項目我們使用非文件的註解,因為 目標對像只有程式碼的讀者,而非公用 API。
測試應一律位於名為 tests
的模組或其子系中。
只包含整合測試的 Crate 不需要 tests
模組,例如:
network/tests/integration、network/tests/fidl。
範例:
// Tests Controller::do_work error returns.
#[test]
fn controller_do_work_errors() { /* ... */ }
測試支援函式應位於名為 testutil
的模組中。如果模組
僅供 In-Crate 使用,應宣告 pub(crate)
,且
#[cfg(test)]
。這個模組不應包含任何 #[test]
函式。如果測試
testutil
模組中的功能需要用到,而該模組名為
tests
(即testutil::tests
)。
偏好恐慌
不要使用 Rust 支援的測試會傳回的測試 結果;這類測試不會自動發出反向追蹤 還是依賴錯誤本身來當做反向追蹤測試未預期的錯誤 反向追蹤通常較難解讀。在本文撰寫期間 Rust 中的回溯追蹤功能不穩定,且 Fuchsia Rust 建構設定中停用,但 所有啟用的錯誤都會包含回溯追蹤;最好能恐慌 仰賴外部因素進行回溯追蹤
匯入
樣式匯入作業適用下列規則。
為每個 Crate 或直接子項模組使用一個 use
陳述式。合併以下項的子項:
這些事項
use child_mod::{Foo, Bar};
use futures::{
stream::{self, futures_unordered::FuturesUnordered},
Future, Stream,
};
一律將已匯入但未參照的特徵設為 _
的別名。可避免雜亂無章的
命名空間,告知讀取器並未參照該 ID。
use futures::FutureExt as _;
避免將其他 Crate 的符號帶入範圍。尤其是如果發生問題
它們的名稱都相似例外狀況適用於廣泛使用、明確易懂的std
類型,例如 HashMap
、HashSet
等
系統一律會從同一個 Crate 匯入符號,包括遵循
宣告 Crate 本機 Error
和 Result
類型的模式。
有些 Crate 非常仰賴外部類型。如果預期用量為 在整個 Crate 中普遍存在,您可以將這些型別匯入為 也就是降低詳細程度,前提是您必須不會製造不確定性。
// DON'T. Parsing this module quickly grows out of hand since it's hard to make
// sense where types are coming from.
mod confusing_mod {
use packet_formats::IpAddress;
use crate::IpAddr;
use fidl_fuchsia_net::Ipv4Address;
}
// DO. Import only types from this crate.
mod happy_mod {
use crate::IpAddress;
fn foo() -> packet_formats::IpAddress { /* .. */ }
fn bar() -> IpAddress { /* .. */ }
fn baz() -> fidl_fuchsia_net::Ipv4Address { /* .. */ }
}
某些知名 Crate 採用了別名或別名建立規則。這些是:
fuchsia_async
的別名可以是fasync
。fuchsia_zircon
的別名可以是zx
。fidl_fuchsia_*
前置字串可以是f*
的別名,例如:use fidl_fuchsia_net_interfaces_admin as fnet_interfaces_admin;
use fidl_fuchsia_net_routes as fnet_routes;
強烈建議您不要匯入 *
。測試從 super
匯入的模組
符合例外情況;測試模組如果不是,則假設測試模組將大部分使用
其父項會宣告所有符號
系統一律會允許在函式主體中匯入類型,因為這麼做的緣故 以掌握型別的來源。
fn do_stuff() {
use some::deeply::nested::{Foo, bar::Bar};
let x = Foo::new();
let y = Bar::new();
/* ... */
}
偏好詳盡的比對結果
盡可能全面比對,避免使用通用模式。比對中 撰寫評論時,完整詳盡地傳達了更多當地資訊,並做為提醒 並在列舉更新時重新造訪,並需要更明確形式來 放置資訊
有些模式會默示打擊完整的相符項目。如果希望 保養:
- 請避免使用
if let
模式,因為這部分方式並不完整。三 達成例外狀況,以允許繫結到Some
Option<T>
,因為這是眾所周知的類型,且None
變體不適用 或任何資訊請注意,如果T
是列舉,if let Some(Foo::Variant)
是 並非有效的例外狀況,因為這可以防止全面比對T
。 - 避免在列舉接收器上指出未比對的變化版本的方法,例如:
is_foo(&self) -> bool
列舉方法,例如Result::is_ok
或Option::is_none
。foo(self) -> Option<T>
列舉方法,例如Result::ok
易於審查的單一變數擷取輔助程式。
Rust 提供 non_exhaustive
屬性,可中斷以下服務的意圖:
完全比對,且 flexible
FIDL 類型加註時
屬性。處理這類類型時,嘗試徹底比對
較有可能過時 - 類型可以在不破壞程式碼的情況下發展,且
因此應避免
// Don't attempt to match exhaustively, exhaustive enumeration is prone to
// becoming stale and misleading future readers if `Foo` takes more variants.
fn bad_flexible_match(foo: fidl_foo::FlexibleFoo) {
match foo {
fidl_foo::FlexibleFoo::Bar => { /* ... */ },
foo @ fidl_foo::FlexibleFoo::Baz |
foo => panic!("unexpected foo {:?}", foo)
}
}
// Use the catch-all pattern instead when the type is non_exhaustive.
fn good_flexible_match(foo: fidl_foo::FlexibleFoo) {
match foo {
fidl_foo::FlexibleFoo::Bar => { /* ... */ },
foo => panic!("unexpected foo {:?}", foo)
}
}
// Note that if the type was not marked non_exhaustive we'd prefer to match
// exhaustively.
fn strict_match(foo: fidl_foo::StrictFoo) {
match foo {
fidl_foo::StrictFoo::Bar => { /* ... */ },
foo @ fidl_foo::StrictFoo::Baz |
foo @ fidl_foo::StrictFoo::Boo => panic!("unexpected foo {:?}", foo)
}
}
TODO(https://github.com/rust-lang/rust/issues/89554):重新造訪
non_exhaustive
non_exhaustive_omitted_patterns
Lint 越趨穩定
避免使用預設類型參數
Rust 支援使用預設的型別參數定義參數化型別。 對於某些參數 (例如限制只在 覆寫測試中的行為例如:
// This can be used as `Foo<X>` or `Foo<X, Y>`.
struct Foo<A, B = u32>(A, B);
// Blanket impl for all possible `Foo`s.
impl<A, B> MyTrait for Foo<A, B> { /* ... */ }
預設類型參數的缺點是,這個方法很容易引發
不完整的大量導入作業。假設 Foo
已使用另一個預設類型參數擴充:
// Now `Foo<A> = Foo<A, u32> = Foo<A, u32, ()>.
struct Foo<A, B = u32, C = ()>(A, B, C);
impl MyTrait
區塊仍未經修改,因此
編譯器 - 涵蓋範圍不完整:只涵蓋 Foo<A, B, ()>
而不是所有可能的 Foo<A, B, C>
。避免使用預設類型參數放置
作者的附著文字 (請一律以「句話」等文字為例)
正確的類型組合
記錄
對開發人員而言,記錄檔是重要的偵錯資源,但很抱歉, 是記錄中減少其價值和信號的陷阱。一併記錄 或嚴重性過高時,並產生雜訊 實際上是對指定問題而言重要的偵錯信號相反地,記錄 如果嚴重性較低,或是嚴重性過低,表示偵錯訊號 一開始可能就發展到記錄中取得平衡至關重要 以便獲取實用的記錄
Netstack3 利用 追蹤 用於記錄的 Crate,其中公開多種記錄層級。深入課程前 Netstack3 元件的記錄指南中,請留意 記錄嚴重性的預設設定:
- 最低記錄嚴重性 (即會忽略低於此值的記錄層級)
已在正式環境設定中設為
info
,使用偵錯時設為debug
網路堆疊元件 (例如測試中)。您可以在 開發人員會選擇在本機執行 Netstack3 的情況。 - 測試最高記錄嚴重性 (此值高於此值的記錄層級會造成
否則傳遞測試為失敗的測試) 設為
warn
: 預設值。個別測試套件可以調高或調低這個門檻。
現在,何時該使用每個記錄層級?
error
:保留給最有可能出現明顯出現的嚴重故障情況 在使用者可見性問題中範例:- 移除介面,因為基礎通訊埠意外關閉。
- 觀察到
PEER_CLOSED
以外的 FIDL 錯誤。
warn
:較嚴重的活動或失敗作業,僅是為了證明error
而已,但 仍可能導致使用者無法看見的問題或其他 元件。範例:- 已成功從未實作的 API 傳回結果。
- 拒絕語意有誤的 API 要求 (例如引數無效)。
- 觀察基礎連接埠的實體狀態已變更為離線。
info
:值得注意的事件和便宜的偵錯信號。範例- 網路狀態變更:例如新增/移除位址、路徑或 存取 API
- 有趣的網路事件 (例如「重複地址偵測」失敗)。
debug
:只與網路堆疊相關的事件和偵錯信號 開發人員。- 控管來自網路的事件,例如路由器通告。
- 傳入的 API 呼叫 (例如通訊端作業或 FIDL 方法) 並傳回給呼叫端的結果
- 開始/停止定期工作,例如「鄰近資料表的垃圾」 收集器。
trace
:除去玩具外昂貴的訊號進行偵錯 環境 (例如單元測試)。範例- 極度頻繁的郵件,例如與傳送/接收的郵件 資料路徑封包,或與高頻率計時器啟動搭配使用的訊息。
我們的目的是是為了程式碼中的 error
和 trace
例項
極少數的情況,在 warn
例項中也很罕見。大部分
記錄檔執行個體應為 info
或 debug
。
請將 debug
設為預設記錄層級選項。決定宣傳前
傳送訊息給info
,問問自己預期的訊息頻率
以及將這個訊息分類至欄位問題時可提供的價值。
在決定向「warn
」宣傳訊息前,請先思考這麼做的可能性
表示特定問題適合其他團隊處理 (例如,適合其他團隊使用)
團隊在記錄檔中看見這則警告,為您指派錯誤)。
這個頁面的變更程序
全體人員獲得邀請,歡迎您針對採用的模式 網路堆疊團隊。團隊會接受或拒絕提議的模式 透過討論建立共識後,可改回使用 go/no-go 單純投票。
請按照下列步驟提出變更建議。
- 編寫並發布變更這個頁面的 CL。
- [選用] 與少數使用者進行社交互動,並反覆測試。
- 透過電子郵件和即時通訊要求整個團隊進行審查。非 Google 員工可以 請透過討論群組與我們聯絡。
- 根據審查留言和離線工作階段來疊代 CL。 請記得將離線工作階段的成果公布回 CL。
- 團隊成員可以表達支持
+1
、反對-1
或異議。 Gerrit 透過單一註解執行緒指出差異性, 成員狀態差異執行緒保持未解決狀態,直到 CL 合併。團隊成員隨時可以變更投票決定。 - 提案審查最多需要 2 週。最後一則通話公告為 第一週結束時收到如果 整個團隊已投票。
- 如果無法達成共識,團隊將會計算票數,並 決定該採用或不要只使用單純大多數
注意事項:
- 作者和主管會透過這個程序做出變更。
- 尊重他人;單靠自己的專業地址
- 避免冗長註解;對佐證論點有異議 簡明扼要
- 不建議在 CL 上來回討論。改回使用分組討論室 現場會議 (建議公開) 並將共識編碼為 留言執行緒
- 如果下列說法有關:您可以捨棄爭議點,並在後續提案中解決 無從得知
- 差異投票是用來衡量編碼部分 例如模式高度差信號會解讀為 並不值得考慮編碼格式。