數值類型
指南:請遵循 Rust 指南,預設為 i32,並在適當時使用明確大小的有正負號和無正負號整數類型。
說明:大多數驅動程式庫編寫者都有 C / C++ 背景,通常會使用未指定大小的有號整數進行算術運算。
指南:在限制條件成立時使用 std::num::NonZero<T> 類型。
說明:確保將零視為特殊情況處理。啟用選項表示法最佳化。
指南:如果零代表特殊情況,請使用 Option<std::num::NonZero<T>> 類型。
說明:確保將零視為特殊情況處理。促使我們在堆疊中較高的位置處理特殊情況,並將不含任何內容的非零類型傳遞至較低層級。
指南:針對會轉換為不可為空值指標的邏輯記憶體位址,使用 std::num::NonZero<usize>;針對可為空值的指標,則使用 Option<std::num::NonZero<usize>>。
說明:根據上述指南,可直接得出結論。Option 類型會強制使用可為空值指標的程式碼,明確處理空值情況。使用建立的指標是不安全的 Rust,後續章節不建議這麼做。
指南:針對傳遞至 Zircon 或從 Zircon 取得的 CPU 實體記憶體位址,請使用 zx_sys::zx_paddr_t。
說明:清楚傳達預期用途,符合 Zircon API 預期的類型。
指南:針對要寫入暫存器的 CPU 或裝置實體記憶體位址,請使用 u32 (如果適用非零假設,則使用 std::num::NonZero<u32>) / u64 (或 std::num::NonZero<u64>)。
例如:
use std::num::NonZero;
use zx_sys::zx_paddr_t;
// Useful displays have at least one pixel.
let display_width: NonZero<u16>;
// Our FIDL APIs use 0 as an invalid ID.
let imported_image_id: Option<NonZero<u64>>;
// To be obtained from a memory pinning API.
let mut image_physical_address: zx_paddr_t;
// Will be written into a register.
let image_physical_address: NonZero<u32>;
// [`None`] when the plane is disabled.
let image_physical_address_reg_value: Option<NonZero<u32>>;
建立執行個體
指南:預設將工廠函式命名為 new()。這個預設值適用於可能失敗和非同步的函式。
說明:符合目前主流的 Rust 用法。
例如:
struct Data {}
impl Data {
pub async fn new() -> Result<Self, zx::Status> { /* ... */ }
}
指南:如果類型也公開了可靠的工廠函式,請使用 try_new() 名稱來表示不可靠的工廠函式。
說明:符合目前主流的 Rust 用法。(不過這種情況很少見)。
指南:實作 From<OtherType> 或 TryFrom<OtherType>,以便從其他類型進行無誤 / 有誤轉換。請勿提供可有效轉換類型的建構函式。
說明:有助於區分轉換和更複雜的執行個體建立作業。
FIDL 繫結
指南:請一律使用 fidl_next 繫結。
說明:驅動程式庫運輸需要 fidl_next 繫結。標準化後,開發人員 (人類和 AI) 就不必在兩種繫結之間切換情境。
未使用的程式碼
指南:使用 #[expect(dead_code)] 並加上說明註解。請勿使用 #[allow(dead_code)]。
說明:#[expect] 版本由編譯器強制執行,因此不會過時。
指南:請勿使用底線 (_) 變數名稱前置字元,而應改用 #[expect(dead_code)]。
說明:底線前置字元等同於 #[allow(dead_code)],因此上述推論適用。
指南:在適用情況下使用底線運算式。如果捨棄值 (例如 Result) 的原因不明顯,請說明原因。
說明:底線運算式與變數名稱中的底線前置字元是不同的語言結構。
例如:
use fdf_component::{Driver, Node};
struct DisplayDriver {
// We must keep the Node alive for the lifetime of the driver.
#[expect(dead_code)]
device_node: Node,
}
impl Driver for DisplayDriver {
async fn stop(&self) {
// Intentionally ignoring failure during device shutdown. There's
// nothing we can do at this point.
let _ = fallible_function_that_logs();
}
}
fn fallible_function_that_logs() -> Result<(), zx::Status> { /* ... */ }
表示法
指南:將 #[repr(...)] 屬性置於所有其他屬性上方。
說明:提早定義表示法,可提高對結構運作的巨集使用正確資料版面配置的可能性。
指南:除非類型的記憶體內表示法必須固定,否則請堅持使用預設表示法 (rust)。只有在直接從驅動程式庫程式與其他軟體或硬體共用的記憶體載入或儲存值時,才必須修正記憶體內表示法。請按照下列規範選擇非預設表示法。
說明:預設表示法可盡量提高編譯器的最佳化機會。如果能保證記憶體是由同一個已編譯二進位檔的多個執行個體使用,我們就可以在共用記憶體中使用 rust 表示法。當有多個軟體 (不同的二進位檔) 或硬體 (裝置) 使用記憶體內的值時,我們必須使用固定的記憶體表示法。
指南:針對需要固定記憶體內表示法的 Rust「newtype」,請使用 #[repr(transparent)]。
說明: #[repr(transparent)] 會對「newtype」意圖進行編碼。編譯器會強制 struct 包裝單一非零大小的類型欄位。
指南:如果多欄位複合類型需要固定的記憶體內表示法,請使用 #[repr(C)]。
說明: #[repr(C)] 會編碼意圖,產生具有確定性欄位偏移和對齊的複合類型。
指南:對於需要固定記憶體內表示法的每個類型,請進行單元測試,檢查每個類型的大小和對齊方式,以及每個欄位的偏移量。
說明:將供應商說明文件轉譯為 Rust 程式碼並非易事,因此我們會使用測試來降低錯誤風險。
指南:指定自訂表示法時,#[derive()]下列特徵:Copy、Clone、zerocopy::FromBytes、zerocopy::Immutable、zerocopy::IntoBytes、zerocopy::KnownLayout。
說明:
Copy可讓您輕鬆推論指標作業Copy必須提供Clonezerocopy::FromBytes證明該類型可用於讀取任何位元模式zerocopy::FromZeros隱含zerocopy::FromByteszerocopy::Immutable證明該類型不使用內部可變性zerocopy::IntoBytes證明該型別可視為位元組序列- 其他
zerocopy個衍生特徵需要zerocopy::KnownLayout
例如:
use bitfield::bitfield;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
bitfield! {
#[repr(transparent)]
#[derive(Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
struct CommandFlags(u32);
/* ... */
}
#[repr(C)]
#[derive(Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
struct Command {
pub flags: CommandFlags,
pub id: u32,
}
#[cfg(test)]
mod tests {
use super::*;
use std::mem::{align_of, offset_of, size_of};
#[fuchsia::test]
fn test_command_abi() {
assert_eq!(size_of::<Command>(), 8);
assert_eq!(align_of::<Command>(), 4);
assert_eq!(offset_of!(Command, flags), 0);
assert_eq!(offset_of!(Command, id), 4);
}
}
use 條路徑
指南:請遵循官方預設值,設定慣用的 use 路徑:
- 納入範圍:類型、衍生和類似屬性的巨集
- 將父項模組納入範圍:函式、函式巨集
說明:符合 The Rust Programming Book 的慣用使用路徑一節中既有的建議。
指南:透過 zx:: 路徑參照 zx 項目。請勿將 zx::Status 等個別項目納入範圍。
說明:
zx crate 會匯出一般類型名稱,例如 Channel 和 Event,這些名稱應與 zx:: 前置字元一起讀取,例如 zx::Event 讀作「Zircon 事件」。這與 Rust 程式設計書籍中關於慣用路徑的部分有所不同,該部分建議將名稱衝突的兩種型別的父項模組納入範圍。
指南:將暫存器或 ABI 定義類型的父項模組納入範圍。在適當情況下,請將模組別名設為 abi 或 registers。
說明:原因與上述規則類似。我們不採用 Rust 慣例,因為暫存器和 ABI 類型名稱可能與 Rust 驅動程式庫類型名稱重疊,因為兩者涵蓋相同的網域。我們在 C++ 驅動程式中重複使用這項做法,在清晰度和簡潔度之間取得良好平衡。
指南:為每個 fidl_next 繫結模組設定別名。所有別名都請使用 fidl_ 前置字元。使用別名來限定對結構體和函式的存取權。
說明:原因同上。FIDL 和 Rust 驅動程式庫類型名稱可能會重疊。
例如:
use fidl_next_fuchsia_sysmem2 as fidl_sysmem2;
pub async fn use_buffer_collection(
sysmem_buffer_collection: &mut fidl_next::Client<fidl_sysmem2::BufferCollection>,
) {
/* ... */
log::warn!("Failed to get hardware pixel formats, falling back to safe set");
/* ... */
}
MMIO 區域管理
指南:所有 MMIO 記憶體區域都應使用 MmioRegion<VmoMemory, Arc<VmoMemory>> 類型。
說明: std::sync::Arc 符合 MmioSplit 特徵限制,允許任何模組進一步細分收到的 MMIO 區域。Arc 與任何執行緒模型相容 (與 Rc 相反)。原子負擔可以忽略不計,因為參照計數只會在驅動程式庫啟動和停止期間變更。
指南:使用 split_off。請勿使用 try_split_off。
說明:硬體會呈現知名的靜態 MMIO 對應。建構地圖的驅動程式庫程式碼不應需要任何條件邏輯。
例如:
use fidl_next_fuchsia_hardware_platform_device as fidl_platform_device;
use mmio::MmioSplit;
use mmio::region::MmioRegion;
use mmio::vmo::VmoMemory;
use std::sync::Arc;
/// Obtains an MMIO region from the Platform Device.
///
/// Logs on failure.
async fn map_mmio_range(
platform_device: &fidl_next::Client<fidl_platform_device::Device>,
range_name: &str,
) -> Result<MmioRegion<VmoMemory, Arc<VmoMemory>>, zx::Status> {
let region = platform_device.map_mmio_by_name(range_name).await.map_err(|err| {
log::error!("Failed to map MMIO range {range_name}: {err:?}");
err.log_to_status()
})?;
Ok(region.into_split_send())
}
impl FunctionalUnit {
pub fn new(mut mmio: MmioRegion<VmoMemory, Arc<VmoMemory>>) -> Self {
// Subunit 1 manages the MMIO range 0x0000..0x1000.
let subunit1_mmio = mmio.split_off(0x1000);
let subunit1 = SubUnit1::new(subunit1_mmio);
// Subunit 2 manages the MMIO range 0x1000..0x2000.
let subunit2_mmio = mmio.split_off(0x1000);
let subunit2 = SubUnit2::new(subunit2_mmio);
// Subunit 3 manages the MMIO range from 0x2000 onwards.
let subunit3 = SubUnit3::new(mmio);
Self { subunit1, subunit2, subunit3 }
}
}
指標和共用記憶體
指南:使用指標存取與硬體共用的記憶體。
說明:Rust 記憶體模型的假設會在建立參照時觸發,不需要存取參照。
指南:立即將系統呼叫傳回的 usize 轉換為 std::num::NonZero<usize>。
說明:強制函式斷言系統呼叫在成功時傳回非空值指標。
指南:如果指標的目標可以透過 Rust 類型表示,請立即將 std::num::NonZero<usize> 轉換為該類型的 std::ptr::NonNull。
說明:盡可能降低發生錯誤的可能性。
指南:需要指標算術時,請立即將 std::num::NonZero<usize> 轉換為 std::ptr::NonNull,然後使用 add() / byte_add() 和 cast() 等方法。
說明:降低出錯機率。指標來源會保留。
例如:
use std::num::NonZero;
use std::ptr::NonNull;
#[repr(...)]
#[derive(...)]
struct Header { /* ... */ }
#[repr(...)]
#[derive(...)]
struct Trailer { /* ... */ }
struct SharedMemory {
header_ptr: NonNull<Header>,
trailer_ptr: NonNull<Trailer>,
}
impl SharedMemory {
pub fn new() -> Result<Self, zx::Status> {
let data_address = fuchsia_runtime::vmar_root_self().map(...)?;
let data_address = NonZero::<usize>::new(data_address)
.expect("zx::vmar::map() returned null address");
// [`Option::unwrap()`] is guaranteed not to panic. The [`NonZero::new()`]
// call above already checked that the pointer is non-null.
let header_ptr = NonNull::new(
std::ptr::with_exposed_provenance_mut(data_address.get())
).unwrap();
// SAFETY: The memory allocation covers both [`Header`] and [`Trailer`].
let trailer_ptr = unsafe { header_ptr.add(1) }.cast::<Trailer>();
Ok(Self { header_ptr, trailer_ptr })
}
}
記錄
指南:只有在回報 Fuchsia 錯誤造成的狀況時,才使用 ERROR 級別。請勿假設傳回錯誤時,ERROR 層級一律適用。
說明:修正對 RFC-0003 的常見誤解。
指南:在驅動程式庫正常運作的情況下,使用 WARNING 層級回報可能發生的硬體故障。
說明:依據 RFC-0003。
指南:僅使用 INFO 層級提供目前調查的背景資訊。在記錄陳述式前方加上註解,並在註解中加入調查追蹤問題的連結。
說明:核心序列記錄包含 INFO 以上層級的驅動程式記錄,用於評估裝置整體健康狀態。
指南:請勿在高頻率程式碼路徑上,記錄 INFO 或更高等級的訊息。如果資料經常變更,請使用「檢查」。
說明:理由同上。
例如:
/// Errors if the hardware returns an invalid version.
///
/// All error conditions are logged.
pub fn read_version() -> Result<u32, zx::Status> {
log::debug!("read_version()");
let version_value: u32 = read_from_register();
// TODO(https://fxbug.dev/12345678): We suspect that the crashes are
// correlated with specific hardware versions. Reduce the log level to DEBUG
// after proving or disproving the hypothesis.
log::info!("Component version: {version_value}");
if version_value == 0 {
log::warn!("Invalid version, device may be off: {version_value}");
// ...
}
// ...
}
使用 try 運算子
指南:建議優先使用 try 運算子 (?),而非其他錯誤處理替代方案。設計函式介面時,請優先考慮呼叫端使用 try 運算子的能力。
說明:簡潔的錯誤處理方式可讓讀者專注於更高層次的內容。try 運算子與 C++ 顯示驅動程式中的錯誤處理相符,其中 zx::result<> 錯誤會向上傳遞至堆疊。
指南:設計傳回值類型中使用的錯誤,以便使用 ?。特別是,建議在 Result 中使用 zx::Status 做為錯誤類型。
說明:目前請採用與 C++ 顯示驅動程式相同的錯誤處理方式。
指南:清楚記錄函式何時會記錄產生錯誤結果的條件。
說明:如果開發人員確定條件已記錄,可以使用 try 運算子。這樣一來,程式碼會更簡潔,也能減少多餘的記錄。
例如:
/// Errors if the hardware returns an invalid version.
///
/// All error conditions are logged.
pub fn read_version() -> Result<u32, zx::Status> { /* ... */ }
/// Initializes the hardware so it can receive commands.
///
/// All error conditions are logged.
pub fn initialize_hardware() -> Result<(), zx::Status> {
let version_value = read_version()?;
/* ... */
}
命名:長度、大小和容量
指南:使用 length 命名變數,計算集合中的元素數量。函式請使用 len()。
說明:符合 Rust 常見做法,例如 Vec::len()。
指南:使用 size_bytes 命名變數和函式,計算用於儲存或傳輸內容的位元組數。請勿建立與叫用 core::mem::size_of 重複的函式。
說明:在 Rust 中,習慣使用「Size」來命名這個概念。 不過,「size」一詞經常出現在硬體相關文件中。「_bytes」後置字串有助於消除歧義。
指南:使用 capacity 命名變數和函式,回報集合記憶體支援的最大元素數量。具體來說,如果集合的備份儲存空間不會變更,則容量固定,且長度會隨著元素的插入和移除而變更。
說明:符合 Rust 常見做法,例如 Vec::capacity。
註解格式
指南:手動換行註解和文件註解,以符合 100 個字元的行數限制。
說明:Fuchsia 的 rustfmt 設定不會啟用不穩定的 wrap_comments 選項。fx format-code 不會納入註解。
準則:即使下列項目超過限制,也不要換行:
@ref(...)、@cite(...)和@alias(...)記錄。工具會將每筆記錄剖析為一行。請參閱參考指南。- 網址和文件內連結。換行會破壞這些元素。
- 無法分割的權杖,例如長 ID 或十六進位酬載。
說明:如果將這些項目換行,會改變其含義或導致工具中斷。
說明文件中的連結
指南:連結 Rustdoc 的依名稱連結功能支援的所有 ID。
說明:Rust API 說明文件指南建議的做法。rust-analyzer 可瀏覽連結。
精選的消歧首碼清單,建議 Rustdoc 可連結的項目:值、常數、原始、模組、函式、類型、typealias、結構體、欄位、方法、特徵、列舉、變體、聯集、巨集、衍生。
範例:
/// See [`DeviceBuilder`] for obtaining instances.
pub struct Device {}
盡量減少巢狀結構層級
指南:呼叫可能失敗的函式後,請立即檢查錯誤,並視需要記錄錯誤並傳回。
說明:人工審查員偏好閱讀以步驟序列呈現的程序,而非巢狀條件區塊。
指南:將非一般錯誤處理作業擷取至專屬函式。簡單的錯誤處理方式是記錄並傳回。
說明:人工審查員較容易分析實作單一程序的函式。
顯示設定和相關項目
指南:將頂層項目設為私人,並放在葉 crate (其他 crate 不會使用的 crate) 中
說明:分葉 Crate 中的公開頂層模組會略過無效程式碼分析。葉 crate 包含驅動程式、整合測試和工具。
指南:請勿在葉 crate 中使用 pub(crate)。請改用 pub。
說明:模組是這些 crate 唯一有意義的 API 屏障。
指南:複合類型的欄位必須全部為私人或全部為公開。「公開」是指 pub 或 pub(crate)。
說明:公開欄位不適用於不變量。
指南:避免在固有的 impl 區塊中,定義簽章不含 Self 的關聯函式。請改為將函式移至簽章中其中一個類型的固有 impl 區塊、將函式與新類型建立關聯,或使用獨立函式。
說明:AI 代理傾向在已讀取的檔案中定義函式,而不是尋找最相關的類型,因此需要加以引導。
例如:
use std::num::NonZero;
/// Packages the information in a FIDL request.
struct MessageRectangle {
pub width: NonZero<u32>,
pub height: NonZero<u32>,
}
impl MessageRectangle {
/// True iff the instance meets the [`HorizontalRectangle`] invariants.
pub fn is_horizontal(&self) -> bool { self.width >= self.height }
}
/// The width is guaranteed to be at least as large as the height.
struct HorizontalRectangle {
width: NonZero<u32>,
height: NonZero<u32>,
}
impl HorizontalRectangle {
/// Factory function.
pub fn square(width: NonZero<u32>) -> Self {
Self { width, height: width }
}
pub fn aspect_ratio(&self) -> f64 { /* ... */ }
}
避免過度使用原始類型
指南:表示雙狀態欄位或參數時,請從下列選項中選擇最容易閱讀的選項:
- 述詞名稱、布林值;範例:
enabled: bool - 名詞名稱,與列舉變體相符;例如:
trigger: Trigger、enum Trigger { Level, Edge }
說明:新開發人員 (AI 和人類) 往往會過度使用布林值。布林值有時比列舉更合適,因此不應全面禁止。
指南:避免使用多個相同類型的非 self 參數定義函式。請改用列舉取代布林值、導入新類型,或將所有引數包裝在 struct 中。重構不符合這項規則的函式。
說明:如果有多個相同類型的參數,呼叫端就很難發現順序切換錯誤。標準範例是交換 width 和 height 引數。
指南:避免在函式呼叫網站上使用 /*argument_name=*/ 註解。請按照上述指南中的解決方案,移除可能造成混淆的內容。
說明:引數名稱註解會掩蓋不當的函式簽章。我們的 C++ 工具支援這種模式,會檢查註解是否與函式宣告中的引數名稱相符。Rust 工具不支援這項功能,因此註解可能會過時。
指南:請勿在具有多個欄位的元組或元組結構上使用元組索引運算式 (.0、.1)。定義具有具名欄位的結構體,或解構您無法控制其類型的值。
說明:人工審查員很難追蹤 .1 這類程式碼所指涉的內容。
例如:
use std::num::NonZero;
enum Polarity {
ActiveHigh,
ActiveLow,
}
/// Argument for [`set_device_timing`].
struct DeviceTiming {
pub horizontal_sync_polarity: Polarity,
pub vertical_sync_polarity: Polarity,
pub horizontal_active: NonZero<u32>,
pub vertical_active: NonZero<u32>,
}
fn set_device_timing(device_timing: DeviceTiming) { /* ... */ }
pub fn initialize_device() {
set_device_timing(DeviceTiming {
horizontal_sync_polarity: Polarity::ActiveHigh,
vertical_sync_polarity: Polarity::ActiveHigh,
horizontal_active: const { NonZero::new(640).unwrap() },
vertical_active: const { NonZero::new(480).unwrap() },
});
}
pub struct Point {
pub x: u32,
pub y: u32,
}
// Returning a struct to avoid tuple indexing expressions.
pub fn cursor_position() -> Point { /* ... */ }
pub fn connect() {
// `zx::Channel::create()` returns `(zx::Channel, zx::Channel)`.
let (driver_channel, device_channel) = zx::Channel::create();
/* ... */
}
同步處理
指南:請使用下列類型的 fuchsia_sync 實作:Condvar、Mutex、RwLock。請勿使用 std::sync 或 parking_lot 中的變體。
說明:fuchsia_sync 實作會在 debug 建構中執行死結偵測。
散文寫作風格
準則:每句只表達一個概念。
說明:AI 代理會盡量減少局部編輯,因此容易產生冗長句。
指南:三個以上的平行項目應列在清單或表格中。只有在項目有值得對齊的屬性時,才使用表格。
說明:具體修正連寫句的主要原因。
指南:理由應自成一句或一段。遵循漸進式揭露原則:先說明規則,再說明理由,最後提供證據。
說明:具體修正連寫句的主要原因。
準則:僅提供能佐證目前理由說明的歷來資訊。避免使用敘述式元素:不必要的事件詳細資料、日期、計畫疊代 ID。刪除已遭取代且不再相關的決策和事實。
說明:AI 代理傾向在註解和文件中建立歷來資訊的小型封存,而非依賴版本記錄進行封存。
指南:在最相關的位置陳述每項事實一次。請使用參照,而非複製。
說明:同上。
準則:盡量簡潔,但仍要清楚。請使用現在式和精確的技術用語。避免主觀細節,大多數形容詞都是主觀的。
說明:調整 AI 代理的語氣,在詞元效率和實用性之間取得平衡。
其他指南
本文著重說明審查 AI 代理生成的程式碼時,常見的問題。
顯示卡驅動程式也遵循下列最佳做法。
- Rust API 指南{.external}
fx format-code實作的 Rust 樣式指南{.external}- rustdoc book{.external}
- Rustonomicon{.external} (主要提供資訊,涵蓋
unsafe程式碼)