後端工坊 2026 年 10 月 2 日

2026-10-02 — Rust 1.99 穩定 C 可變參數函式定義

primary=https://blog.rust-lang.org/2026/10/01/Rust-1.99.0/ primary=https://doc.rust-lang.org/stable/releases.html#version-1990-2026-10-01 primary=https://doc.rust-lang.org/std/boxed/struct.Box.html#method.leak

Rust 1.99 穩定 C 可變參數函式定義,不再只能呼叫 printf

Rust Blog · 2026-10-01

Rust 過去只能「呼叫」別人用 C 寫好的可變參數函式,現在可以自己「定義」:unsafe extern "C" fn f(args: ...) 在 1.99.0 進入 stable。Rust Release Team 於 2026-10-01 發布此版本,同版另有 Layout::for_value_raw 等三個 raw pointer 版 layout API,以及 Box::leak 的文件警告。

背景

C 的可變參數函式(printf、open 這類簽名裡帶 ... 的函式)靠 va_list 在呼叫端與被呼叫端之間傳遞參數。官方公告指出,Rust 早就能呼叫外部定義的可變參數函式(例如 libc::printf),但在 stable 上寫出這種函式本身做不到。

這對 以 Rust 實作 C ABI 函式庫的專案是明顯缺口:要對外提供 int log_msg(const char *fmt, ...) 這種 C 介面,只能繞過 Rust 的定義語法。公告未說明過去各專案實際採用的繞法,此處不推測。

核心改動:... 與 VaList

1.99 讓 "C" 與 "C-unwind" ABI 的函式可以用 ... 宣告參數。... 的型別是 VaList,公告說它在各 target 上都與 C 的 va_list 具 ABI 相容。能從 VaList 讀出哪些型別,由 VaArgSafe trait 限制。

/// SAFETY: must be called with (at least) 2 i32 arguments.
unsafe extern "C" fn sum(mut args: ...) -> i32 {
    let a = unsafe { args.next_arg::<i32>() };
    let b = unsafe { args.next_arg::<i32>() };
    a + b
}

fn foo() -> i32 {
    unsafe { sum(0i32, 2i32) }
}

上面是公告的範例(省略了一行註解)。注意兩點:函式本身必須是 unsafe,因為呼叫端傳了幾個、什麼型別,被呼叫端無從檢查;讀取每個參數的 next_arg 也各自需要 unsafe。

同一版還穩定了用 #[unsafe(naked)] 搭配內嵌組語,定義非 "C" ABI 的 naked 可變參數函式。release notes 把它列為獨立項目,旗標名稱是 c_variadic_naked_functions。

實務上,這代表 Rust 端可以直接成為 C 呼叫端的被呼叫者,不必再多一層 C 檔案轉接。範例中 sum 沒有具名參數,args 就是整條參數列,所以 sum(0i32, 2i32) 的兩個值都由 next_arg 讀出;若呼叫端少傳一個,讀取就是未定義行為,這正是 SAFETY 註解要求「至少 2 個 i32」的原因。

讀取型別受 VaArgSafe 約束,意味著不是任意 Rust 型別都能從 VaList 取出;公告未列出實作該 trait 的完整型別清單,需查 trait 文件。

1.98 以前1.99
呼叫 C 的可變參數函式可以可以
用 Rust 定義可變參數函式stable 不行可以,"C" 與 "C-unwind"

同版另一個值得注意的改動:Box::leak

語言語意在 1.99 沒變,但 Box::leak 的文件改為明確建議不要把洩漏出去的記憶體再還原並釋放。公告給的理由是,這種寫法與現在及未來可能的編譯器最佳化有問題的互動,在 custom allocator 即將穩定後尤其嚴重。

文件現在的說法是:只有 allocator 為 Global 時才有可能用 Box::from_raw 還原,而且屬灰色地帶,許多看似無害的寫法其實是 undefined behavior。需要之後釋放的話,改用 Box::into_raw 或新穩定的 Box::into_non_null;公告表示這項指引也適用於標準庫其他 leak 函式。

// 不建議:leak 之後又想 free
let r: &'static mut T = Box::leak(b);
let b = unsafe { Box::from_raw(r) };

// 建議:一開始就用 into_raw / into_non_null
let p = Box::into_raw(b);
let b = unsafe { Box::from_raw(p) };

這項調整同時指向一個趨勢:unsafe 程式碼的前提正被逐步寫成文件。1.99 的 release notes 另外寫明,部分配置允許原地擴大但不允許縮小,並保證 UnsafeCell 的內容不經過 get 也能存取,同時調整了 invalid_reference_casting lint。寫 allocator 或底層容器的人,應重讀這些條目,確認自己的假設沒有落在新寫明的界線外。

影響範圍

  • 用 Rust 寫對外 C 介面的函式庫作者:可以直接以 extern "C" fn f(args: ...) 實作 printf 風格的入口。要確認每個 next_arg::<T>() 的 T 與呼叫端實際傳入的型別一致,這個前提只能靠 unsafe 契約與文件保證。
  • 程式碼裡有「Box::leak 後再 Box::from_raw」的 crate:搜尋這個模式,改成 into_raw 或 into_non_null,尤其是準備使用 custom allocator 的專案。
  • 需要取得 ?Sized 值 layout 的 unsafe 程式:Layout::for_value_raw、mem::size_of_val_raw、mem::align_of_val_raw 現在可在 stable 對 raw pointer 使用,公告表示這些函式一併定下了 Sized 與非 Sized 型別的安全需求。
  • 依賴 RangeInclusive 耗盡後狀態的程式:release notes 說明為了更好的迭代最佳化,已耗盡的 a..=b 其 start()、end() 回傳值與當作 slice index 的行為可能不同,官方稱這些行為本來就未保證穩定。
  • 跑 riscv64 musl 的 CI:riscv64-unknown-linux-musl 升為 Tier 2 並附 host tools。

其餘 stabilized API(如 Vec::into_parts、VecDeque::retain_back、std::fs::set_times)本文不展開,細節見 release notes。

原始來源:Announcing Rust 1.99.0、Rust 1.99.0 release notes、Box::leak 文件


End of article
0
Would love your thoughts, please comment.x
()
x