diff --git a/src/items/functions.md b/src/items/functions.md index 362b39b573..399e8f18fe 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 identifier, such as `args: ...`, and in a [c-variadic function definition], the identifier is mandatory. r[items.fn.body] ## Function body @@ -332,6 +332,120 @@ 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(ap: ...) -> f64 { + unsafe { ap.next_arg::() } +} +``` + +This parameter stands in for an arbitrary number of arguments that may be passed by the caller. + +[items.fn.c-variadic.stable-targets] +Support for c-variadic function definitions is stable on the following architectures: + +- x86 and x86-64 +- ARM +- AArch64 and Arm64EC +- RISC-V (except when using the ilp32e ABI) +- LoongArch +- s390x +- PowerPC and PowerPC64 +- AmdGpu and Nvptx64 +- wasm32 and wasm64 +- csky +- xtensa +- hexagon +- sparc64 +- mips + +r[items.fn.c-variadic.c-variadic-parameter-type] +The type of `pat` in the function body is [`VaList`]. + +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 +# use std::ffi::VaList; +// Desugared +unsafe extern "C" fn example() -> i32 { + let mut ap: VaList<'_> = /* compiler initializes the VaList */; + + unsafe { ap.next_arg::() } + + va_end(ap) +} +``` + +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.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 type 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.ffi-compatibility] +The rust [`VaList`] is ABI-compatible with the C `va_list` type. + +r[items.fn.c-variadic.abi] +Only `extern "C"` and `extern "C-unwind"` function defintions 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 functions cannot be `async` + +r[items.fn.c-variadic.const] +A c-variadic functions cannot be `const` + +r[items.fn.c-variadic.platform-support] +Some ABIs do not support c-variadic function definitions. The compiler errors in this case. + +```text +error: the `bpfel` target does not support c-variadic functions + --> $DIR/not-supported.rs:23:31 + | +LL | unsafe extern "C" fn variadic(_: ...) {} + | ^^^^^^ +``` + r[items.fn.attributes] ## Attributes on functions @@ -424,6 +538,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 26e03aee8c..4c3c38cd98 100644 --- a/src/special-types-and-traits.md +++ b/src/special-types-and-traits.md @@ -63,6 +63,12 @@ r[lang-types.phantom-data] 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]. The rust type is +FFI-compatible with the C `va_list` type. + r[lang-types.ops] ## Operator traits @@ -226,6 +232,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#c-variadic-functions [deref coercions]: type-coercions.md#coercion-types [dereference operator]: expressions/operator-expr.md#the-dereference-operator [destructor]: destructors.md