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 文件