From c2b023dbcac457ef3ef6a6c06975436e1263ebc8 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 17 Feb 2026 19:33:27 +0100 Subject: [PATCH 1/7] add c-variadic function definitions --- src/items/external-blocks.md | 2 +- src/items/functions.md | 124 +++++++++++++++++++++++++++++++- src/items/traits.md | 1 + src/special-types-and-traits.md | 9 +++ 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index e547edf8a1..d942648377 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -212,7 +212,7 @@ Like `"C"` and `"system"`, most platform-specific ABI strings also have a [corre r[items.extern.variadic] ## Variadic functions -Functions within external blocks may be variadic by specifying `...` as the last argument. The variadic parameter may optionally be specified with an identifier. +Functions within external blocks may be variadic by specifying `...` as the last argument. The variadic parameter may optionally be specified with a pattern. ```rust unsafe extern "C" { diff --git a/src/items/functions.md b/src/items/functions.md index 362b39b573..f7baf3a679 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -83,7 +83,7 @@ r[items.fn.params.self-restriction] Functions with a self parameter may only appear as an [associated function] in a [trait] or [implementation]. r[items.fn.params.varargs] -A parameter with the `...` token indicates a [variadic function], and may only be used as the last parameter of an [external block] function. The variadic parameter may have an optional identifier, such as `args: ...`. +A parameter with the `...` token indicates a [c-variadic function], and may only be used as the last parameter. In an [`extern` block], the c-variadic parameter may have an optional pattern, such as `args: ...`, and in a [c-variadic function definition][items.fn.c-variadic], the pattern is mandatory. r[items.fn.body] ## Function body @@ -332,6 +332,125 @@ Note that this behavior is a consequence of the desugaring to a function that re Unsafe is used on an async function in precisely the same way that it is used on other functions: it indicates that the function imposes some additional obligations on its caller to ensure soundness. As in any other unsafe function, these conditions may extend beyond the initial call itself -- in the snippet above, for example, the `unsafe_example` function took a pointer `x` as argument, and then (when awaited) dereferenced that pointer. This implies that `x` would have to be valid until the future is finished executing, and it is the caller's responsibility to ensure that. +r[items.fn.c-variadic] +## C-variadic functions + +r[items.fn.c-variadic.intro] +A *c-variadic* function accepts a variable argument list `pat: ...` as its final parameter. + +```rust +unsafe extern "C" fn example(mut ap: ...) -> f64 { + unsafe { ap.next_arg::() } +} +``` + +This parameter stands in for an arbitrary number of arguments that may be passed by the caller. + +r[items.fn.c-variadic.c-variadic-parameter-type] +The type of `pat` in the function body is [`VaList`]. + +r[items.fn.c-variadic.lifetime] +The lifetime of a `VaList` is that of the function that created it. Hence, the `VaList` value can never outlive the function that created it. + +r[items.fn.c-variadic.desugar-brief] +A c-variadic function definition is roughly equivalent to a function operating on a [`VaList`]. + +```rust +// Source +unsafe extern "C" fn example(mut ap: ...) -> i32 { + unsafe { ap.next_arg::() } +} +``` + +is roughly equivalent to: + + +```rust,no_run +# #![ feature(core_intrinsics) ] +# #![allow(internal_features)] +use core::ffi::VaList; +use core::mem::MaybeUninit; +use core::intrinsics::{va_arg, va_end}; +// va_start is magic and has no intrinsic. +fn va_start(ap: *mut VaList<'_>) { /* magic */ } +// Desugared +unsafe extern "C" fn example() -> i32 { + unsafe { + let mut ap: MaybeUninit> = MaybeUninit::uninit(); + va_start(ap.as_mut_ptr()); + let mut ap = ap.assume_init(); + + let x = va_arg::(&mut ap); + + va_end(&mut ap); + + x + } +} +``` + +r[items.fn.c-variadic.next-arg-safety] +Calling `VaList::next_arg` to read an argument of type `T` is only safe if all of the following conditions are satisfied: + +- There is another c-variadic argument to read. +- The actual type of the argument `U` is compatible with `T` (as defined below). +- If `U` and `T` are both integer types, then the value passed by the caller must be +representable in both types. + +Types `T` and `U` are compatible when: + +- `T` and `U` are the same type (up to free lifetimes). +- `T` and `U` are integer types of the same size. +- `T` and `U` are both pointers, and their target types are compatible. +- `T` is a pointer to `c_void` and `U` is a pointer to `i8` or `u8`, or vice versa. + +Examples of compatible types are: + +- `u32` and `i32` --- but UB may still occur if the value is not representable in the target type. +- `u64` and `usize` --- on a 64-bit platform. +- `*const &'a u32` and `*mut &'static u32` --- these types are equal up to free lifetimes. + +Examples of incompatible types are: + +- `usize` and `*const _` --- pointers and integers are not compatible. +- `*const fn(&'static ())` and `*const for<'a> fn(&'a ())` --- these types are not equal up to free lifetimes. + +r[items.fn.c-variadic.abi-compatibility] +[`VaList`] is ABI-compatible with the C `va_list` type. + +r[items.fn.c-variadic.abi] +Only `extern "C"` and `extern "C-unwind"` function definitions can accept a variable argument list. + +r[items.fn.c-variadic.safety] +Only `unsafe` functions can accept a variable argument list. + +r[items.fn.c-variadic.async] +A c-variadic function cannot be `async`. + +r[items.fn.c-variadic.const] +A c-variadic function cannot be `const`. + +r[items.fn.c-variadic.stable-targets] +Support for c-variadic function definitions is stable on the following target architectures: + +- x86 and x86-64 +- ARM +- AArch64 and Arm64EC +- RISC-V 32-bit and 64-bit (except when using the ilp32e ABI) +- LoongArch 32-bit and 64-bit +- s390x +- PowerPC and PowerPC64 +- AmdGpu and Nvptx64 +- Wasm32 and Wasm64 +- C-SKY +- Xtensa +- Hexagon +- SPARC64 +- MIPS + +> [!NOTE] +> Some target architectures (e.g. BPF) do not support c-variadic function definitions. The compiler errors in this case. + r[items.fn.attributes] ## Attributes on functions @@ -424,6 +543,7 @@ fn foo_oof(#[some_inert_attribute] arg: u8) { [associated function]: associated-items.md#associated-functions-and-methods [implementation]: implementations.md [value namespace]: ../names/namespaces.md -[variadic function]: external-blocks.md#variadic-functions +[c-variadic function]: external-blocks.md#variadic-functions [`extern` block]: external-blocks.md [zero-sized]: glossary.zst +[`VaList`]: std::ffi::VaList diff --git a/src/items/traits.md b/src/items/traits.md index fe1a985f53..e525e94d23 100644 --- a/src/items/traits.md +++ b/src/items/traits.md @@ -100,6 +100,7 @@ r[items.traits.dyn-compatible.associated-functions] * Not be an `async fn` (which has a hidden `Future` type). * Not have a return position `impl Trait` type (`fn example(&self) -> impl Trait`). * Not have a `where Self: Sized` bound (receiver type of `Self` (i.e. `self`) implies this). + * Not have a c-variadic argument `_: ...`. * Explicitly non-dispatchable functions require: * Have a `where Self: Sized` bound (receiver type of `Self` (i.e. `self`) implies this). diff --git a/src/special-types-and-traits.md b/src/special-types-and-traits.md index dddefda040..04a61989f0 100644 --- a/src/special-types-and-traits.md +++ b/src/special-types-and-traits.md @@ -53,6 +53,14 @@ r[lang-types.phantom-data] [`std::marker::PhantomData`] is a [zero-sized], minimum alignment, type that is considered to own a `T` for the purposes of [variance], [drop check], and [auto traits](#auto-traits). +r[lang-types.va-list] +## `VaList<'_>` + +[`std::ffi::VaList<'_>`] is used for [c-variadic functions]. + +> [!NOTE] +> The Rust `VaList` type is ABI-compatible with the C `va_list` type, see [items.fn.c-variadic.abi-compatibility]. + r[lang-types.ops] ## Operator traits @@ -189,6 +197,7 @@ These implicit `Sized` bounds may be relaxed by using the special `?Sized` bound [Arrays]: types/array.md [associated types]: items/associated-items.md#associated-types [call expressions]: expressions/call-expr.md +[c-variadic functions]: items/functions.md#c-variadic-functions [deref coercions]: type-coercions.md#coercion-types [dereference operator]: expressions/operator-expr.md#the-dereference-operator [destructor]: destructors.md From 404985f9a542da3379fc9cbaeeb1f01ac7aee9e4 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 21 Jul 2026 21:34:16 +0000 Subject: [PATCH 2/7] Fix the `items.fn.c-variadic.safety` rule It's a bit complicated which declarations are required to be `unsafe` when using variadic argument lists; let's enumerate the cases. --- src/items/functions.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/items/functions.md b/src/items/functions.md index f7baf3a679..35be845517 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -422,7 +422,14 @@ r[items.fn.c-variadic.abi] Only `extern "C"` and `extern "C-unwind"` function definitions can accept a variable argument list. r[items.fn.c-variadic.safety] -Only `unsafe` functions can accept a variable argument list. +When a variable argument list is used in the signature: + +- Function definitions must be `unsafe`. +- Function declarations within trait definitions must be `unsafe`. +- Function declarations in `extern` blocks may be `safe`. + +> [!NOTE] +> For `safe` function declarations in an `extern` block, see the warning in [items.extern.variadic]. r[items.fn.c-variadic.async] A c-variadic function cannot be `async`. From bfdd99fa99f8a2db1119d79d6fc26242c9915192 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Thu, 23 Jul 2026 09:49:34 +0000 Subject: [PATCH 3/7] Revise C-variadic rules editorially --- src/items/external-blocks.md | 2 +- src/items/functions.md | 47 +++++++++++++++------------------ src/items/traits.md | 2 +- src/special-types-and-traits.md | 7 ++--- 4 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/items/external-blocks.md b/src/items/external-blocks.md index d942648377..9b1c013796 100644 --- a/src/items/external-blocks.md +++ b/src/items/external-blocks.md @@ -212,7 +212,7 @@ Like `"C"` and `"system"`, most platform-specific ABI strings also have a [corre r[items.extern.variadic] ## Variadic functions -Functions within external blocks may be variadic by specifying `...` as the last argument. The variadic parameter may optionally be specified with a pattern. +Functions within external blocks may be made variadic by specifying `...` as the last parameter. The variadic parameter may be specified with a pattern. ```rust unsafe extern "C" { diff --git a/src/items/functions.md b/src/items/functions.md index 35be845517..0bf2eda3a9 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -83,7 +83,7 @@ r[items.fn.params.self-restriction] Functions with a self parameter may only appear as an [associated function] in a [trait] or [implementation]. r[items.fn.params.varargs] -A parameter with the `...` token indicates a [c-variadic function], and may only be used as the last parameter. In an [`extern` block], the c-variadic parameter may have an optional pattern, such as `args: ...`, and in a [c-variadic function definition][items.fn.c-variadic], the pattern is mandatory. +A parameter with the `...` token indicates a [C-variadic function] and may only be used as the last parameter. In an [`extern` block], the C-variadic parameter may have a pattern, such as `args: ...`, and in a [C-variadic function definition], the pattern is mandatory. r[items.fn.body] ## Function body @@ -336,7 +336,7 @@ r[items.fn.c-variadic] ## C-variadic functions r[items.fn.c-variadic.intro] -A *c-variadic* function accepts a variable argument list `pat: ...` as its final parameter. +A *C-variadic* function accepts a variable argument list `pat: ...` as its final parameter. ```rust unsafe extern "C" fn example(mut ap: ...) -> f64 { @@ -346,44 +346,39 @@ unsafe extern "C" fn example(mut ap: ...) -> f64 { This parameter stands in for an arbitrary number of arguments that may be passed by the caller. -r[items.fn.c-variadic.c-variadic-parameter-type] -The type of `pat` in the function body is [`VaList`]. +r[items.fn.c-variadic.parameter-type] +The type of `pat` in the function body is [`VaList<'_>`]. r[items.fn.c-variadic.lifetime] -The lifetime of a `VaList` is that of the function that created it. Hence, the `VaList` value can never outlive the function that created it. +The lifetime of a `VaList` is that of the function call that created it. r[items.fn.c-variadic.desugar-brief] -A c-variadic function definition is roughly equivalent to a function operating on a [`VaList`]. +A C-variadic function definition is roughly equivalent to a function operating on a [`VaList`]. ```rust -// Source unsafe extern "C" fn example(mut ap: ...) -> i32 { unsafe { ap.next_arg::() } } ``` -is roughly equivalent to: +Roughly desugars to: ```rust,no_run # #![ feature(core_intrinsics) ] # #![allow(internal_features)] -use core::ffi::VaList; -use core::mem::MaybeUninit; +# use core::ffi::VaList; +# use core::mem::MaybeUninit; use core::intrinsics::{va_arg, va_end}; -// va_start is magic and has no intrinsic. +// `va_start` is magic and has no intrinsic. fn va_start(ap: *mut VaList<'_>) { /* magic */ } -// Desugared unsafe extern "C" fn example() -> i32 { unsafe { let mut ap: MaybeUninit> = MaybeUninit::uninit(); va_start(ap.as_mut_ptr()); let mut ap = ap.assume_init(); - let x = va_arg::(&mut ap); - va_end(&mut ap); - x } } @@ -392,7 +387,7 @@ unsafe extern "C" fn example() -> i32 { r[items.fn.c-variadic.next-arg-safety] Calling `VaList::next_arg` to read an argument of type `T` is only safe if all of the following conditions are satisfied: -- There is another c-variadic argument to read. +- There is another C-variadic argument to read. - The actual type of the argument `U` is compatible with `T` (as defined below). - If `U` and `T` are both integer types, then the value passed by the caller must be representable in both types. @@ -401,7 +396,7 @@ Types `T` and `U` are compatible when: - `T` and `U` are the same type (up to free lifetimes). - `T` and `U` are integer types of the same size. -- `T` and `U` are both pointers, and their target types are compatible. +- `T` and `U` are both pointers and their target types are compatible. - `T` is a pointer to `c_void` and `U` is a pointer to `i8` or `u8`, or vice versa. Examples of compatible types are: @@ -416,7 +411,7 @@ Examples of incompatible types are: - `*const fn(&'static ())` and `*const for<'a> fn(&'a ())` --- these types are not equal up to free lifetimes. r[items.fn.c-variadic.abi-compatibility] -[`VaList`] is ABI-compatible with the C `va_list` type. +[`VaList`] is ABI compatible with the C `va_list` type. r[items.fn.c-variadic.abi] Only `extern "C"` and `extern "C-unwind"` function definitions can accept a variable argument list. @@ -432,13 +427,13 @@ When a variable argument list is used in the signature: > For `safe` function declarations in an `extern` block, see the warning in [items.extern.variadic]. r[items.fn.c-variadic.async] -A c-variadic function cannot be `async`. +A C-variadic function cannot be `async`. r[items.fn.c-variadic.const] -A c-variadic function cannot be `const`. +A C-variadic function cannot be `const`. r[items.fn.c-variadic.stable-targets] -Support for c-variadic function definitions is stable on the following target architectures: +Support for C-variadic function definitions is stable on the following target architectures: - x86 and x86-64 - ARM @@ -447,7 +442,7 @@ Support for c-variadic function definitions is stable on the following target ar - LoongArch 32-bit and 64-bit - s390x - PowerPC and PowerPC64 -- AmdGpu and Nvptx64 +- AMDGPU and NVPTX - Wasm32 and Wasm64 - C-SKY - Xtensa @@ -456,7 +451,7 @@ Support for c-variadic function definitions is stable on the following target ar - MIPS > [!NOTE] -> Some target architectures (e.g. BPF) do not support c-variadic function definitions. The compiler errors in this case. +> Some target architectures (e.g., BPF) do not support C-variadic function definitions. The compiler will emit an error if such a definition is used on an unsupported target. r[items.fn.attributes] ## Attributes on functions @@ -550,7 +545,9 @@ fn foo_oof(#[some_inert_attribute] arg: u8) { [associated function]: associated-items.md#associated-functions-and-methods [implementation]: implementations.md [value namespace]: ../names/namespaces.md -[c-variadic function]: external-blocks.md#variadic-functions +[C-variadic function]: items.fn.c-variadic.intro +[C-variadic function definition]: items.fn.c-variadic [`extern` block]: external-blocks.md +[`VaList<'_>`]: lang-types.va-list +[`VaList`]: lang-types.va-list [zero-sized]: glossary.zst -[`VaList`]: std::ffi::VaList diff --git a/src/items/traits.md b/src/items/traits.md index e525e94d23..043c1773a4 100644 --- a/src/items/traits.md +++ b/src/items/traits.md @@ -100,7 +100,7 @@ r[items.traits.dyn-compatible.associated-functions] * Not be an `async fn` (which has a hidden `Future` type). * Not have a return position `impl Trait` type (`fn example(&self) -> impl Trait`). * Not have a `where Self: Sized` bound (receiver type of `Self` (i.e. `self`) implies this). - * Not have a c-variadic argument `_: ...`. + * Not have a C-variadic parameter (`_: ...`). * Explicitly non-dispatchable functions require: * Have a `where Self: Sized` bound (receiver type of `Self` (i.e. `self`) implies this). diff --git a/src/special-types-and-traits.md b/src/special-types-and-traits.md index 04a61989f0..3f7589113e 100644 --- a/src/special-types-and-traits.md +++ b/src/special-types-and-traits.md @@ -56,10 +56,10 @@ r[lang-types.phantom-data] r[lang-types.va-list] ## `VaList<'_>` -[`std::ffi::VaList<'_>`] is used for [c-variadic functions]. +[`VaList`] is used for [C-variadic functions]. > [!NOTE] -> The Rust `VaList` type is ABI-compatible with the C `va_list` type, see [items.fn.c-variadic.abi-compatibility]. +> [`VaList`] is ABI compatible with the C `va_list` type; see [items.fn.c-variadic.abi-compatibility]. r[lang-types.ops] ## Operator traits @@ -197,7 +197,7 @@ These implicit `Sized` bounds may be relaxed by using the special `?Sized` bound [Arrays]: types/array.md [associated types]: items/associated-items.md#associated-types [call expressions]: expressions/call-expr.md -[c-variadic functions]: items/functions.md#c-variadic-functions +[C-variadic functions]: items.fn.c-variadic [deref coercions]: type-coercions.md#coercion-types [dereference operator]: expressions/operator-expr.md#the-dereference-operator [destructor]: destructors.md @@ -220,6 +220,7 @@ These implicit `Sized` bounds may be relaxed by using the special `?Sized` bound [trait object]: types/trait-object.md [Tuples]: types/tuple.md [Type parameters]: types/parameters.md +[`VaList`]: core::ffi::VaList [variance]: subtyping.md#variance [zero-sized]: glossary.zst [Closures]: types/closure.md From 7a8825483f958feb9b40d1583cb65d73a780def4 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Thu, 23 Jul 2026 19:06:11 +0000 Subject: [PATCH 4/7] Make `items.fn.c-variadic.lifetime` more precise The statement that the "lifetime of a `VaList` is that of the function (call) that created it" feels a bit loose to me. Let's pin that down more precisely. --- src/items/functions.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/items/functions.md b/src/items/functions.md index 0bf2eda3a9..c15046c537 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -350,7 +350,36 @@ r[items.fn.c-variadic.parameter-type] The type of `pat` in the function body is [`VaList<'_>`]. r[items.fn.c-variadic.lifetime] -The lifetime of a `VaList` is that of the function call that created it. +A C-variadic function definition is implicitly generic over the lifetime of its variadic parameter, as if the parameter had type `VaList<'x>` for a fresh, unnameable lifetime `'x`. Because the function must be valid for any such lifetime, the `VaList` cannot be proved to outlive any caller-provided lifetime (and so cannot escape the call) and no caller-provided lifetime can be proved to outlive it. + +```rust,compile_fail +# use core::ffi::VaList; +fn b_outlives_a<'a, 'b: 'a>(_: &mut VaList<'a>, _: &mut &'b mut u8) {} +unsafe extern "C" fn f(mut r: &mut u8, mut ap: ...) { + b_outlives_a(&mut ap, &mut r); // ERROR: Lifetime may not live long enough. +} +``` + +```rust,compile_fail +# use core::ffi::VaList; +fn a_outlives_b<'a: 'b, 'b>(_: &mut VaList<'a>, _: &mut &'b mut u8) {} +unsafe extern "C" fn f(mut r: &mut u8, mut ap: ...) { + a_outlives_b(&mut ap, &mut r); // ERROR: Lifetime may not live long enough. +} +``` + +> [!NOTE] +> This is different than if the data were a stack variable: any caller-provided lifetime can be proved to outlive a borrow of a callee stack variable. +> +> ```rust +> struct MockVaList<'data>(&'data u8); +> fn b_outlives_a<'a, 'b: 'a>(_: &mut MockVaList<'a>, _: &mut &'b mut u8) {} +> unsafe extern "C" fn f(mut r: &mut u8) { +> let data = 0; +> let mut ap = MockVaList(&data); +> b_outlives_a(&mut ap, &mut r); // OK. +> } +> ``` r[items.fn.c-variadic.desugar-brief] A C-variadic function definition is roughly equivalent to a function operating on a [`VaList`]. @@ -384,6 +413,9 @@ unsafe extern "C" fn example() -> i32 { } ``` +> [!NOTE] +> In an actual C-variadic function definition, the lifetime in `VaList<'_>` is different from what this code would suggest. See [items.fn.c-variadic.lifetime]. + r[items.fn.c-variadic.next-arg-safety] Calling `VaList::next_arg` to read an argument of type `T` is only safe if all of the following conditions are satisfied: From 4a0af5ee2f23bf4e41cb2fab460ddb8c8397751f Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Thu, 23 Jul 2026 23:11:40 +0000 Subject: [PATCH 5/7] Add examples to the C-variadic rules We're moving in the direction of having one or more examples for each rule where possible. Let's add examples to the new rules where they were missing and make sense to have. --- src/items/functions.md | 69 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/src/items/functions.md b/src/items/functions.md index c15046c537..0f80840c16 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -339,16 +339,27 @@ r[items.fn.c-variadic.intro] A *C-variadic* function accepts a variable argument list `pat: ...` as its final parameter. ```rust -unsafe extern "C" fn example(mut ap: ...) -> f64 { +unsafe extern "C" fn f(mut ap: ...) -> f64 { unsafe { ap.next_arg::() } } ``` +```rust,compile_fail +unsafe extern "C" fn f(ap: ..., _: ()) {} // ERROR: `...` must be last. +``` + This parameter stands in for an arbitrary number of arguments that may be passed by the caller. r[items.fn.c-variadic.parameter-type] The type of `pat` in the function body is [`VaList<'_>`]. +```rust +# use core::ffi::VaList; +unsafe extern "C" fn f(ap: ...) { + let _: VaList<'_> = ap; +} +``` + r[items.fn.c-variadic.lifetime] A C-variadic function definition is implicitly generic over the lifetime of its variadic parameter, as if the parameter had type `VaList<'x>` for a fresh, unnameable lifetime `'x`. Because the function must be valid for any such lifetime, the `VaList` cannot be proved to outlive any caller-provided lifetime (and so cannot escape the call) and no caller-provided lifetime can be proved to outlive it. @@ -356,7 +367,7 @@ A C-variadic function definition is implicitly generic over the lifetime of its # use core::ffi::VaList; fn b_outlives_a<'a, 'b: 'a>(_: &mut VaList<'a>, _: &mut &'b mut u8) {} unsafe extern "C" fn f(mut r: &mut u8, mut ap: ...) { - b_outlives_a(&mut ap, &mut r); // ERROR: Lifetime may not live long enough. + b_outlives_a(&mut ap, &mut r); // ERROR: May not live long enough. } ``` @@ -364,7 +375,7 @@ unsafe extern "C" fn f(mut r: &mut u8, mut ap: ...) { # use core::ffi::VaList; fn a_outlives_b<'a: 'b, 'b>(_: &mut VaList<'a>, _: &mut &'b mut u8) {} unsafe extern "C" fn f(mut r: &mut u8, mut ap: ...) { - a_outlives_b(&mut ap, &mut r); // ERROR: Lifetime may not live long enough. + a_outlives_b(&mut ap, &mut r); // ERROR: May not live long enough. } ``` @@ -385,7 +396,7 @@ r[items.fn.c-variadic.desugar-brief] A C-variadic function definition is roughly equivalent to a function operating on a [`VaList`]. ```rust -unsafe extern "C" fn example(mut ap: ...) -> i32 { +unsafe extern "C" fn f(mut ap: ...) -> i32 { unsafe { ap.next_arg::() } } ``` @@ -401,7 +412,7 @@ Roughly desugars to: use core::intrinsics::{va_arg, va_end}; // `va_start` is magic and has no intrinsic. fn va_start(ap: *mut VaList<'_>) { /* magic */ } -unsafe extern "C" fn example() -> i32 { +unsafe extern "C" fn f() -> i32 { unsafe { let mut ap: MaybeUninit> = MaybeUninit::uninit(); va_start(ap.as_mut_ptr()); @@ -445,9 +456,33 @@ Examples of incompatible types are: r[items.fn.c-variadic.abi-compatibility] [`VaList`] is ABI compatible with the C `va_list` type. +```rust +# use core::ffi::{c_char, c_int, VaList}; +unsafe extern "C" { + // The C `vprintf` function is: + // + // int vprintf(const char *format, va_list ap); + // + unsafe fn vprintf(fmt: *const c_char, ap: VaList<'_>) -> c_int; +} + +unsafe extern "C" fn print(fmt: *const c_char, ap: ...) -> c_int { + // The `VaList` is passed directly to the C function. + unsafe { vprintf(fmt, ap) } +} +``` + r[items.fn.c-variadic.abi] Only `extern "C"` and `extern "C-unwind"` function definitions can accept a variable argument list. +```rust,compile_fail +unsafe fn f(ap: ...) {} // ERROR: Not supported. +``` + +```rust,compile_fail +unsafe extern "sysv64" fn f(ap: ...) {} // ERROR: Not supported. +``` + r[items.fn.c-variadic.safety] When a variable argument list is used in the signature: @@ -455,15 +490,39 @@ When a variable argument list is used in the signature: - Function declarations within trait definitions must be `unsafe`. - Function declarations in `extern` blocks may be `safe`. +```rust,compile_fail +extern "C" fn f(ap: ...) {} // ERROR: Must be `unsafe`. +``` + +```rust,compile_fail +trait Tr { + extern "C" fn f(ap: ...); // ERROR: Must be `unsafe`. +} +``` + +```rust +unsafe extern "C" { + safe fn f(ap: ...); // OK. +} +``` + > [!NOTE] > For `safe` function declarations in an `extern` block, see the warning in [items.extern.variadic]. r[items.fn.c-variadic.async] A C-variadic function cannot be `async`. +```rust,compile_fail +async unsafe extern "C" fn f(ap: ...) {} // ERROR: Cannot be `async`. +``` + r[items.fn.c-variadic.const] A C-variadic function cannot be `const`. +```rust,compile_fail,E0658 +const unsafe extern "C" fn f(ap: ...) {} // ERROR: Cannot be `const`. +``` + r[items.fn.c-variadic.stable-targets] Support for C-variadic function definitions is stable on the following target architectures: From f303ad073917bca755e6dd341c53d29009f0c689 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Fri, 24 Jul 2026 00:18:48 +0000 Subject: [PATCH 6/7] Clarify `items.fn.params.varargs` WRT declarations The pattern is required when the variadic parameter appears either on a function definition or on a function declaration in a trait definition. Let's say that and add examples. --- src/items/functions.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/items/functions.md b/src/items/functions.md index 0f80840c16..c35704e2f8 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -83,7 +83,30 @@ r[items.fn.params.self-restriction] Functions with a self parameter may only appear as an [associated function] in a [trait] or [implementation]. r[items.fn.params.varargs] -A parameter with the `...` token indicates a [C-variadic function] and may only be used as the last parameter. In an [`extern` block], the C-variadic parameter may have a pattern, such as `args: ...`, and in a [C-variadic function definition], the pattern is mandatory. +A parameter with the `...` token indicates a [C-variadic function] and may only be used as the last parameter. In a function declaration in an [`extern` block], the C-variadic parameter may have a pattern, such as `ap: ...`, and in a [C-variadic function] definition or C-variadic associated function declaration in a trait definition, the pattern is mandatory. + +```rust +unsafe extern "C" { + unsafe fn f1(...); + unsafe fn f2(ap: ...); +} + +unsafe extern "C" fn f3(ap: ...) {} + +trait Tr { + unsafe extern "C" fn f4(ap: ...); +} +``` + +```rust,compile_fail +unsafe extern "C" fn f(...) {} // ERROR: Missing pattern. +``` + +```rust,compile_fail +trait Tr { + unsafe extern "C" fn f(...); // ERROR: Missing pattern. +} +``` r[items.fn.body] ## Function body @@ -637,7 +660,6 @@ fn foo_oof(#[some_inert_attribute] arg: u8) { [implementation]: implementations.md [value namespace]: ../names/namespaces.md [C-variadic function]: items.fn.c-variadic.intro -[C-variadic function definition]: items.fn.c-variadic [`extern` block]: external-blocks.md [`VaList<'_>`]: lang-types.va-list [`VaList`]: lang-types.va-list From a729f2d224f1ef2d9d9e7da0554203879d819c32 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Fri, 24 Jul 2026 00:32:56 +0000 Subject: [PATCH 7/7] Add note about `rustc` `varargs_without_pattern` behavior Though we document that function definitions and associated function declarations in trait definitions must have a pattern, that's only true due to an FCW. Let's add an admonition about that. --- src/items/functions.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/items/functions.md b/src/items/functions.md index c35704e2f8..062053e058 100644 --- a/src/items/functions.md +++ b/src/items/functions.md @@ -108,6 +108,21 @@ trait Tr { } ``` +> [!NOTE] +> `rustc` currently accepts `...` without a pattern in function definitions and associated function declarations in trait definitions while linting against it. This will become an error in the future. +> +> ```rust +> #![allow(varargs_without_pattern)] +> unsafe extern "C" fn f(...) {} // OK. +> ``` +> +> ```rust +> #![allow(varargs_without_pattern)] +> trait Tr { +> unsafe extern "C" fn f(...); // OK. +> } +> ``` + r[items.fn.body] ## Function body