英語

編碼規範

本文件描述瞭如何在核心中編寫 Rust 程式碼。

樣式 & 格式

程式碼應使用 rustfmt 格式化。 這樣,偶爾為核心做貢獻的人就不需要學習和記住另一個風格指南。 更重要的是,審閱者和維護者不再需要花費時間指出樣式問題,因此可能需要更少的補丁往返才能提交更改。

注意

關於註釋和文件的約定不受 rustfmt 的檢查。 因此,仍然需要注意這些。

使用 rustfmt 的預設設定。 這意味著遵循慣用的 Rust 風格。 例如,縮排使用 4 個空格而不是製表符。

指示編輯器/IDE 在輸入、儲存或提交時進行格式化很方便。 但是,如果由於某種原因需要在某個時候重新格式化整個核心 Rust 原始碼,則可以執行以下命令

make LLVM=1 rustfmt

也可以檢查所有內容是否已格式化(否則列印差異),例如對於 CI,可以使用

make LLVM=1 rustfmtcheck

就像核心其餘部分的 clang-format 一樣,rustfmt 對單個檔案起作用,並且不需要核心配置。 有時它甚至可以處理損壞的程式碼。

註釋

“普通”註釋(即 //,而不是以 /////! 開頭的程式碼文件)以 Markdown 編寫,就像文件註釋一樣,即使它們不會被呈現。 這提高了程式碼一致性,簡化了規則,並允許更輕鬆地在這兩種註釋之間移動內容。 例如

// `object` is ready to be handled now.
f(object);

此外,就像文件一樣,註釋在句首大寫,並以句點結尾(即使它是一個句子)。 這包括 // SAFETY:, // TODO: 和其他“標記”註釋,例如

// FIXME: The error should be handled properly.

註釋不應用於文件目的:註釋旨在用於實現細節,而不是使用者。 即使原始檔的讀者既是 API 的實現者又是使用者,這種區別也很有用。 事實上,有時同時使用註釋和文件是有用的。 例如,對於 TODO 列表或評論文件本身。 對於後一種情況,可以將註釋插入中間; 也就是說,更靠近要註釋的文件行。 對於任何其他情況,註釋都寫在文件之後,例如

/// Returns a new [`Foo`].
///
/// # Examples
///
// TODO: Find a better example.
/// ```
/// let foo = f(42);
/// ```
// FIXME: Use fallible approach.
pub fn f(x: i32) -> Foo {
    // ...
}

這適用於公共和私有專案。 這增加了與公共專案的一致性,允許以更少的更改參與更改可見性,並且將允許我們潛在地為私有專案生成文件。 換句話說,如果為私有專案編寫文件,則仍應使用 ///。 例如

/// My private function.
// TODO: ...
fn f() {}

一種特殊的註釋是 // SAFETY: 註釋。 這些必須出現在每個 unsafe 塊之前,並且它們解釋了為什麼塊內的程式碼是正確的/健全的,即為什麼它在任何情況下都不會觸發未定義的行為,例如

// SAFETY: `p` is valid by the safety requirements.
unsafe { *p = 0; }

// SAFETY: 註釋不應與程式碼文件中的 # Safety 部分混淆。 # Safety 部分指定了呼叫者(對於函式)或實現者(對於特徵)需要遵守的約定。 // SAFETY: 註釋顯示了為什麼呼叫(對於函式)或實現(對於特徵)實際上尊重 # Safety 部分或語言參考中陳述的先決條件。

程式碼文件

Rust 核心程式碼的文件記錄方式與 C 核心程式碼不同(即透過 kernel-doc)。 相反,使用了記錄 Rust 程式碼的常用系統:rustdoc 工具,它使用 Markdown(一種輕量級標記語言)。

要學習 Markdown,有很多可用的指南。 例如,在

這是一個文件完善的 Rust 函式的樣子

/// Returns the contained [`Some`] value, consuming the `self` value,
/// without checking that the value is not [`None`].
///
/// # Safety
///
/// Calling this method on [`None`] is *[undefined behavior]*.
///
/// [undefined behavior]: https://doc.rust-lang.org/reference/behavior-considered-undefined.html
///
/// # Examples
///
/// ```
/// let x = Some("air");
/// assert_eq!(unsafe { x.unwrap_unchecked() }, "air");
/// ```
pub unsafe fn unwrap_unchecked(self) -> T {
    match self {
        Some(val) => val,

        // SAFETY: The safety contract must be upheld by the caller.
        None => unsafe { hint::unreachable_unchecked() },
    }
}

此示例展示了一些 rustdoc 功能和核心中遵循的一些約定

  • 第一段必須是一個簡單的一句話,簡要描述記錄的專案的作用。 進一步的解釋必須放在額外的段落中。

  • 不安全的函式必須在其 # Safety 部分下記錄其安全前提條件。

  • 雖然此處未顯示,但如果函式可能發生 panic,則必須在 # Panics 部分下描述發生這種情況的條件。

    請注意,panic 應該非常罕見,並且僅在有充分理由時才使用。 在幾乎所有情況下,都應使用易錯方法,通常返回 Result

  • 如果提供用法示例可以幫助讀者,則必須將它們寫在一個名為 # Examples 的部分中。

  • 必須適當地連結 Rust 專案(函式、型別、常量...)(rustdoc 將自動建立一個連結)。

  • 任何 unsafe 塊必須以 // SAFETY: 註釋開頭,描述為什麼內部的程式碼是健全的。

    雖然有時原因可能看起來微不足道,因此不需要,但編寫這些註釋不僅是記錄已考慮事項的好方法,而且最重要的是,它提供了一種知道沒有 *額外的* 隱式約束的方法。

要了解有關如何編寫 Rust 文件和額外功能的更多資訊,請檢視 rustdoc 書籍,網址為

此外,核心支援建立相對於源樹的連結,方法是在連結目標前面加上 srctree/。 例如

//! C header: [`include/linux/printk.h`](srctree/include/linux/printk.h)

/// [`struct mutex`]: srctree/include/linux/mutex.h

C FFI 型別

Rust 核心程式碼使用類型別名(例如 c_int)引用 C 型別(例如 int),這些類型別名可從 kernel prelude 中輕鬆獲得。 請不要使用來自 core::ffi 的別名——它們可能無法對映到正確的型別。

這些別名通常應透過其識別符號直接引用,即作為單個段路徑。 例如

fn f(p: *const c_char) -> c_int {
    // ...
}

命名

Rust 核心程式碼遵循常見的 Rust 命名約定

當現有的 C 概念(例如宏、函式、物件...)被包裝到 Rust 抽象中時,應使用盡可能接近 C 端的名字,以避免混淆,並在 C 和 Rust 端來回切換時提高可讀性。 例如,來自 C 的宏(例如 pr_info)在 Rust 端使用相同的名稱。

話雖如此,應調整大小寫以遵循 Rust 命名約定,並且不應在專案名稱中重複由模組和型別引入的名稱空間。 例如,包裝像這樣的常量時

#define GPIO_LINE_DIRECTION_IN  0
#define GPIO_LINE_DIRECTION_OUT 1

Rust 中的等效項可能如下所示(忽略文件)

pub mod gpio {
    pub enum LineDirection {
        In = bindings::GPIO_LINE_DIRECTION_IN as _,
        Out = bindings::GPIO_LINE_DIRECTION_OUT as _,
    }
}

也就是說,GPIO_LINE_DIRECTION_IN 的等效項將稱為 gpio::LineDirection::In。 特別是,它不應命名為 gpio::gpio_line_direction::GPIO_LINE_DIRECTION_IN

Lints

在 Rust 中,可以本地 allow 特定警告(診斷、lints),使編譯器忽略給定函式、模組、塊等中給定的警告例項。

它類似於 C 中的 #pragma GCC diagnostic push + ignored + pop [1]

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
static void f(void) {}
#pragma GCC diagnostic pop

但是簡潔得多

#[allow(dead_code)]
fn f() {}

憑藉這一優點,它可以舒適地預設啟用更多診斷(即在 W= 級別之外)。 特別是,那些可能有少量誤報,但在其他方面非常有用,可以保持啟用以捕獲潛在的錯誤。

最重要的是,Rust 提供了 expect 屬性,它更進一步。 如果未產生警告,它會使編譯器發出警告。 例如,以下將確保,當在某處呼叫 f() 時,我們將不得不刪除該屬性

#[expect(dead_code)]
fn f() {}

如果我們不這樣做,我們會收到來自編譯器的警告

warning: this lint expectation is unfulfilled
 --> x.rs:3:10
  |
3 | #[expect(dead_code)]
  |          ^^^^^^^^^
  |
  = note: `#[warn(unfulfilled_lint_expectations)]` on by default

這意味著當不需要 expect 時,它們不會被遺忘,這可能發生在幾種情況下,例如

  • 開發時新增的臨時屬性。

  • 編譯器、Clippy 或自定義工具中的 lint 改進,可能會消除誤報。

  • 由於預計會在某個時候刪除 lint,因此當不再需要 lint 時,例如上面的 dead_code 示例。

它還提高了其餘 allow 的可見性,並減少了錯誤應用一個的機會。

因此,除非出現以下情況,否則請首選 expect 而不是 allow

  • 條件編譯在某些情況下會觸發警告,但在其他情況下則不會。

    如果與總案例數相比,只有少數案例觸發(或不觸發)警告,則可以考慮使用條件 expect(即 cfg_attr(..., expect(...)))。 否則,很可能只是使用 allow 更簡單。

  • 在宏內部,當不同的呼叫可能建立擴充套件程式碼,該程式碼在某些情況下會觸發警告,而在其他情況下則不會。

  • 當代碼可能為某些架構觸發警告,而為其他架構觸發警告時,例如 as 強制轉換為 C FFI 型別。

作為一個更完善的例子,例如考慮以下程式

fn g() {}

fn main() {
    #[cfg(CONFIG_X)]
    g();
}

在這裡,如果未設定 CONFIG_X,則函式 g() 是 dead code。 我們可以在這裡使用 expect 嗎?

#[expect(dead_code)]
fn g() {}

fn main() {
    #[cfg(CONFIG_X)]
    g();
}

如果設定了 CONFIG_X,這將發出一個 lint,因為它在該配置中不是 dead code。 因此,在這種情況下,我們不能按原樣使用 expect

一種簡單的可能性是使用 allow

#[allow(dead_code)]
fn g() {}

fn main() {
    #[cfg(CONFIG_X)]
    g();
}

另一種選擇是使用條件 expect

#[cfg_attr(not(CONFIG_X), expect(dead_code))]
fn g() {}

fn main() {
    #[cfg(CONFIG_X)]
    g();
}

這將確保,如果有人在某處引入對 g() 的另一個呼叫(例如,無條件),那麼就會發現它不再是 dead code。 但是,cfg_attr 比簡單的 allow 更復雜。

因此,當涉及多個或兩個以上的配置,或者由於非本地更改(例如 dead_code)可能觸發 lint 時,可能不值得使用條件 expect

有關 Rust 中診斷的更多資訊,請參閱

錯誤處理

有關 Rust for Linux 特定錯誤處理的一些背景資訊和指南,請參閱