Skip to content

Stabilize c-variadic function definitions#155697

Open
folkertdev wants to merge 1 commit intorust-lang:mainfrom
folkertdev:stabilize-c-variadic
Open

Stabilize c-variadic function definitions#155697
folkertdev wants to merge 1 commit intorust-lang:mainfrom
folkertdev:stabilize-c-variadic

Conversation

@folkertdev
Copy link
Copy Markdown
Contributor

@folkertdev folkertdev commented Apr 23, 2026

tracking issue: #44930
reference PR: rust-lang/reference#2177

There are some minor things still in flight, but I think we're far enough along now.

Stabilization report

Summary

In C, functions can use a variable argument list ... to accept an arbitrary number of untyped arguments. Rust is already able to call such functions (e.g. libc::printf), the c_variadic feature adds the ability to define them.

A rust c-variadic function looks like this:

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

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

This function accepts a variable arguments list args: ..., from which it is able to read arguments using the next_arg method.

The main goal of defining c-variadic functions in rust is interaction with C code. Therefore it is a design goal that the rust types map directly to their C counterparts. Additionally, we disallow interaction between c-variadic functions and certain rust features that don't make much sense in an FFI context (e.g. extern "Rust" fn or async fn).

How variadics work in C

The authoritative source for how variadics (also known as "variable arguments") work in C is the C specification. In this document we'll use section 7.16 of the final draft of the C23 standard.

A function may be called with a variable number of arguments of varying types if its parameter type list ends with an ellipsis

Earlier versions of C furthermore required that the ... argument is not the first argument of the parameter type list (so at least one other argument was required). Starting in C23 this requirement has been lifted.

C API surface

  • va_list: an opaque type that stores the information needed to read variadic arguments, or copy the list of variadic arguments. Typically values of this type get the name ap.
  • va_start: a va_list must be initialized with the va_start macro before it can be used.
  • va_copy: the va_copy macro copies a va_list. The copy starts at the position in the argument list of the original (so not at the first variadic argument to the function), and both can be moved forward independently. This means the same argument can be read multiple times.
  • va_arg: reads the next argument from the va_ist.
  • va_end: deinitializes a va_list.

Important notes

Not calling va_end is UB

Section 7.16.1

Each invocation of the va_start and va_copy macros shall be matched by a corresponding invocation of the va_end macro in the same function.

Section 7.16.1.3:

The va_end macro facilitates a normal return from the function whose variable argument list was referred to by the expansion of the va_start macro, or the function containing the expansion of the va_copy macro, that initialized the va_list ap. The va_end macro may modify ap so that it is no longer usable (without being reinitialized by the va_start or va_copy macro). If there is no corresponding invocation of the va_start or va_copy macro, or if the va_end macro is not invoked before the return, the behavior is undefined.

We believe that this behavior is this strict because some early C implementations chose to implement va_list like so (source):

#define         va_start(ap, parmN)     {\
        va_buf  _va;\
        _vastart(ap = (va_list)_va, (char *)&parmN + sizeof parmN)
#define         va_end(ap)      }
#define         va_arg(ap, mode)        *((mode *)_vaarg(ap, sizeof (mode)))

To our knowledge no remotely-modern implementation actually implements va_end as anything but a no-op.

A va_list may be moved

Section 7.16

The object ap may be passed as an argument to another function; if that function invokes the va_arg macro with parameter ap, the representation of ap in the calling function is indeterminate and shall be passed to the va_end macro prior to any further reference to ap.

and

A pointer to a va_list can be created and passed to another function, in which case the original function can make further use of the original list after the other function returns

So va_list can be moved into another function, but va_end must still run in the frame that initialized (with va_start or va_copy) the va_list .

Representation of va_list

The representation of va_list is platform-specific. There are three flavors that are used by current rust targets:

  • va_list is an opaque pointer
  • va_list is a struct
  • va_list is a single-element array, containing a struct

The opaque pointer approach is the simplest to implement: the pointer just points to an array of arguments on the caller's stack.

The struct and single-element array variants are more complex, but potentially more efficient because the additional state makes it possible to pass c-variadic arguments via registers.

array-to-pointer decay

If the va_list is of the single-element array flavor, it is subject to array-to-pointer decay: in C, arrays are passed not by-value, but as pointers. Hence, from an FFI perspective, these two functions are equivalent.

#include <stdarg.h>

extern int foo(va_list va) {
    return va_arg(va, int);
}

extern int bar(va_list *va) {
    return va_arg(*va, int);
}

Indeed, they generate the same assembly, see https://godbolt.org/z/n8c4aq5hM.

other calling conventions

Both clang and gcc refuse to compile a function that uses variadic arguments and a non-default calling convention. See also #141618, in particular #141618 (comment).

Hence the calling convention of a c-variadic function definition implicitly uses the default C ABI on the current platform.

va_arg and argument promotion

With some exceptions, the return type of a va_arg call must match the type of the supplied argument:

Section 7.16.1

If type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined

These default argument promotions are specified in section 6.5.3.3:

The arguments are implicitly converted, as if by assignment, to the types of the corresponding parameters, taking the type of each parameter to be the unqualified version of its declared type. The ellipsis notation in a function prototype declarator causes argument type conversion to stop after the last declared parameter, if present. The integer promotions are performed on each trailing argument, and trailing arguments that have type float are promoted to double. These are called the default argument promotions. No other conversions are performed implicitly

There are a couple of additional conversions that are allowed, such as casting signed to/from unsigned integers.

A concrete example of what is not allowed is to use va_arg to read (signed or unsigned) char or short, or float arguments. Reading such types is UB. See also #61275 (comment).

How c-variadics work in rust

Rust API surface

The rust API has similar capabilities to C but uses rust names and concepts.

pub struct VaList<'f> { 
    /* ... */
    _marker: PhantomCovariantLifetime<'f>,
}

The VaList struct has a lifetime parameter that ensures that the VaList cannot outlive the function that created it. Semantically VaList contains mutable references (to the caller's and/or callee's stack), so the lifetime is covariant.

The #[rustc_pass_indirectly_in_non_rustic_abis] attribute is applied to the definition if va_list is a single-element array on the target platform. This attribute simulates the array-to-pointer decay that the C va_list type is subject to on that target. By using this attribute, C va_list and Rust VaList are FFI-compatible.

This attribute works as desired because VaList is a by-move type: in rust this is enforced by the type system, in C the specification requires it. Mutations to the passed object are not observable in the caller (because the value is semantically moved and hence inaccessible).

On targets where va_list is just a pointer or a struct the C and rust types are already FFI-compatible, so no special attribute is needed there.

pub unsafe trait VaArgSafe: Sealed {}

impl<'f> VaList<'f> {
    pub unsafe fn next_arg<T: VaArgSafe>(&mut self) -> T { /* ... */ }
}

The VaList::arg method can be used to read the next argument. The implementation uses va_arg (though in most cases we re-implement the logic in rustc itself, see below). The return type is constrained by VaArgSafe so that only valid argument types can be read. In particular this mechanism prevents subtle issues around implicit numeric promotion in C. Reading an argument is unsafe because reading more arguments than were supplied is UB.

The VaArgSafe trait is guaranteed to be implemented for:

  • c_int, c_long and c_longlong
  • c_uint, c_ulong and c_ulonglong
  • c_double
  • *const T and *mut T

Implementations for other types are not guaranteed to be portable, so portable programs should not rely on e.g. usize or f64 implementing this trait directly.

C argument types are considered to have the rust type that corresponds to core::ffi::*, so a C int is mapped to c_int and so on. We don't consider the C _BitInt or _Float32 types here, rather we (on most platforms) map f64 to double etc. _BitInt(32) and int are distinct types. _BitInt is furthermore special because it does not participate in integer promotion.

The rust implementation requires that the caller and callee agree on the exact type. This is slightly more conservative than C, which does allow e.g. conversion between signed and unsigned integers, or to/from void pointers. Reading a value as a different type than it was passed as is undefined behavior.

fn caller() -> i32 {
    // This call violates the safety contract.
    unsafe { variadic(1u32) }
}

// SAFETY: the caller must supply an i32 argument, optionally followed by other arguments that are ignored.
unsafe extern "C" fn variadic(mut ap: ...) { 
    unsafe { ap.next_arg::<i32>() }
    // Miri/const eval will report this UB.
}

The VaList type implements Clone and Drop:

impl<'f> Clone for VaList<'f> { /* ... */ } 

impl<'f> Drop for VaList<'f> { 
    fn drop(&mut self) {
        /* no-op */
    }
}

The Clone implementation can be used to duplicate a VaList. The copy has the same position as the original, but both can be incremented independently. While all current targets could also implement Copy for VaList, a future target might not, so for now Copy is not implemented.

The Drop implementation is a no-op. This choice is based on the assumption that in rust it is safe to not run a destructor. Additionally, va_end is a no-op for all current LLVM targets. In C,va_end must run in the frame where the va_list was initialized. Because VaList can be moved (like the C va_list), the frame in which a VaList is dropped may not be the frame in which it was initialized.

The VaList type is available on all targets, even on targets that don't actually support c-variadic definitions. On such targets, it is impossible to get a valid VaList value, because attempting to define a c-variadic function (using the ... argument) will throw an error.

Syntax

In rust, a C-variadic function looks like this:

unsafe extern "C" fn foo(a: i32, b: i32, args: ...) {
    /* body */
}

The special args: ... argument stands in for an arbitrary number of arguments that the caller may pass. The ... argument must be the last argument in the parameter list of a function. Like in C23 and later, ... may be the only argument. The ... syntax is already stable in foreign functions, c_variadic additionally allows it in function definitions.

In function definitions, the ... argument must have a pattern. The argument can be ignored by using _: .... In foreign function declarations the pattern can be omitted.

A function definition with a ... argument must be an unsafe function. Passing an incorrect number of arguments, or arguments of the wrong type, is UB, and hence every call site has to satisfy the safety conditions. A special case is a function that ignores its VaList entirely using _: ...: we may decide to allow such functions to be safe. At the time of writing we see insufficient benefits relative to the additional complexity that this entails.

A function with the ... argument must be an extern "C" or extern "C-unwind" function. In the future we want to extend the set of accepted ABIs to include all ABIs for which we allow calling a c-variadic function (including e.g. sysv64 and win64).

The ... argument can occur in definitions of functions, inherent methods, and trait methods. When any method on a trait uses a c-variadic argument, the trait is no longer dyn-compatible. The technical reason is that there is no sound way to generate a ReifyShim that passes on the c-variadic arguments.

Desugaring

In a function like this:

unsafe extern "C" fn foo(args: ...) {
    // ...
}

The args: ... is internally desugared into a call to LLVM's va_start that initializes args as a VaList, and a call to LLVM's va_end on every return path. The VaList gets the lifetime of a local variable on foo's stack, so that the VaList cannot outlive the function that created it.

The desugaring will fail with an error when the current target does not support c-variadic definitions. Currently this is the case for spriv and bpf:

error: the `bpfel` target does not support c-variadic functions
  --> $DIR/not-supported.rs:23:31
   |
LL | unsafe extern "C" fn variadic(_: ...) {}
   |                               ^^^^^^

A note on LLVM va_arg

The LLVM va_arg intrinsic is known to silently miscompile. A comment in the implementation notes:

This default implementation defers to the llvm backend's va_arg instruction. It can handle only passing arguments directly (typically only handled in the backend for primitive types), or aggregates passed indirectly by pointer (NOTE: if the "byval" flag has ABI impact in the callee, this implementation cannot work.)

Only a few cases are covered here at the moment -- those needed by the default abi.

Hence, like clang, rustc implements va_arg for the vast majority of targets (specifically including all tier-1 targets) in va_arg.rs. Note that the match on the architecture is exhaustive. If no custom implementation is provided, the LLVM implementation is used as a fallback. This fallback is likely to work, but may silently miscompile the program.

Future extensions

C-variadics and const fn

Support for c-variadic const fn (and by extension, support in Miri) is implemented in
#150601 and gated by const_c_variadic. Practical usage requires const_destruct too because VaList has a custom Drop implementation.

#![feature(c_variadic, const_c_variadic, const_destruct)]

const unsafe extern "C" fn variadic(mut ap: ...) -> i32 {
    ap.next_arg()
}

Naked variadic functions

Currently only C and C-unwind are valid ABIs for all c-variadic function definitions. With naked functions it is possible to define e.g. a win64 c-variadic function in a program where sysv64 is the default. This feature is tracked as c_variadic_naked_functions.

#![feature(c_variadic, c_variadic_naked_functions)]

#[unsafe(naked)]
unsafe extern "win64" fn variadic_win64(_: u32, _: ...) -> u32 {
    core::arch::naked_asm!(
        r#"
        push    rax
        mov     qword ptr [rsp + 40], r9
        mov     qword ptr [rsp + 24], rdx
        mov     qword ptr [rsp + 32], r8
        lea     rax, [rsp + 40]
        mov     qword ptr [rsp], rax
        lea     eax, [rdx + rcx]
        add     eax, r8d
        pop     rcx
        ret
    "#,
    )
}

C-variadics and coroutines

An async fn or any other type of coroutine cannot be c-variadic. We see no reason to support this.

Defining safe C-variadic functions

In extern blocks, it is valid to mark C-variadic functions as safe, under the assumption that the function completely ignores the variable arguments list:

unsafe extern "C" {
    safe fn foo(...);
}

Normally, C-variadic function definitions must be unsafe, because calling the function with unexpected (in type or number) elements is UB. We could relax this constraint on C-variadic functions that ignore their C variable argument list, e.g.:

// NOTE: not unsafe
extern "C" fn(x: i32, _: ...) -> i32 {
    x
}

At the moment we don't have a good reason to add this behavior. It is completely backwards compatible, so if a need arises in the future we can revisit this.

Accepting more va_arg return types

Discussed in #44930 (comment).

We only want to implement VaArgSafe for types that have a clear counterpart in C. That rules out types like Option<NonNull> or NonZeroI32. We might add MaybeUninit<c_int> and so on in the future if a use case comes up: this would map to a C union.

The only planned extension right now is support for i128 and u128 where they can be mapped to (unsigned) __int128. However rustc currently does not know on what targets this type is available, and hence we leave it out of the stabilization for now.

Adding 128-bit support is in progress in #155429.

Multiple C-variadic ABIs in the same program

#141618

Both clang and gcc reject using ... in functions with a non-default ABI for the target. That makes the layout of VaList and expansion of va_start, va_arg etc. unambiguous. For now we impose a similar restriction for the rust implementation. This restriction could be lifted in the future, but this would requite that VaList somehow "stores" its ABI.

One approach is to add a type parameter to VaList that default's to the platform's default ABI. Each c-variadic argument would then desugar to use the ABI of the c-variadic function that creates it.

History

RFC 2137 proposes to "support defining C-compatible variadic functions in rust" in 2017, and it is still the core of the implementation today. The text lays out a basic rust API and highlights potential issues (e.g. some solution is needed to match C's array-to-pointer decay), but does not always provide concrete solutions.

In 2019 #59625 introduces a wrapper type to simulate array-to-pointer decay. With this API the C semantics can be matched, but doing so correctly takes a great deal of care. The VaList type also has two lifetime arguments in this version, which is inelegant.

Then, little seems to have happened for 6 years, until the recent burst of activity that resulted in the current proposal.

implementation history

The list of PRs is long, but they have all been labled with F-c_variadic.

Unresolved Questions

VaArgSafe and function pointers

#153646

Currently VaArgSafe is not implemented for function pointers, and doing so would be tricky. There is the practical issue of not being able to be generic over the number of arguments, but there are also some complex constraints on the signature, see https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Compatible-Types.html.

Handling niche tier-3 targets

See #44930 (comment).

There are some tier-3 targets where we're not 100% confident that the implementation is correct.

  • mips
  • 32-bit sparc
  • m64k and msp430
  • custom targets that use a custom target json file with an unrecognized architecture

There are no known issues, and to our knowledge we match clang in all cases, but these targets are niche and hard to run (no_std, compilers are not easily available, etc.), and the target maintainers have not confirmed that the current implementation works. How should we handle this?

  • just do what clang does (i.e. defer to LLVM's va_arg) and hope for the best. these are all tier-3 targets, so shrug
  • use bug! to trigger an ICE on those targets
  • gate the implementation behind a feature, e.g. c_variadic_experimental_arch

My personal preference is the feature gate, because it unblocks users, we're actually quite confident in the implementation being correct, and to find any lingering issues we'd need to be able to try it.

Thanks

Many people have worked on this feature over the years, and many more have provided input. I'd like to credit here the people that have been especially involved in this push for stabilization: @workingjubilee, @RalfJung, @beetrees, @joshtriplett and @tgross35.

r? @tgross35

@folkertdev folkertdev added the F-c_variadic `#![feature(c_variadic)]` label Apr 23, 2026
@rustbot rustbot added A-run-make Area: port run-make Makefiles to rmake.rs F-explicit_tail_calls `#![feature(explicit_tail_calls)]` PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Apr 23, 2026
@rust-log-analyzer

This comment has been minimized.

@folkertdev folkertdev force-pushed the stabilize-c-variadic branch from d1f9d2c to d7ccbf8 Compare April 23, 2026 20:37
@rust-log-analyzer

This comment has been minimized.

@folkertdev folkertdev force-pushed the stabilize-c-variadic branch from d7ccbf8 to f5739d8 Compare April 23, 2026 21:54
@juntyr
Copy link
Copy Markdown
Contributor

juntyr commented Apr 24, 2026

There's a typo above: "The VaList type implements Copy and Drop" should be "Clone and Drop"

@rust-bors

This comment has been minimized.

@folkertdev folkertdev force-pushed the stabilize-c-variadic branch from f5739d8 to 50f6221 Compare April 24, 2026 18:05
@folkertdev folkertdev added I-lang-nominated Nominated for discussion during a lang team meeting. S-waiting-on-t-lang Status: Awaiting decision from T-lang labels Apr 24, 2026
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 24, 2026

tgross35 is currently at their maximum review capacity.
They may take a while to respond.

@folkertdev folkertdev marked this pull request as ready for review April 24, 2026 18:17
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Apr 24, 2026

Some changes occurred to the intrinsics. Make sure the CTFE / Miri interpreter
gets adapted for the changes, if necessary.

cc @rust-lang/miri, @RalfJung, @oli-obk, @lcnr

The Miri subtree was changed

cc @rust-lang/miri

Some changes occurred in tests/ui/sanitizer

cc @rcvalle

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Apr 24, 2026
@folkertdev folkertdev removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Apr 24, 2026
@tgross35 tgross35 added the needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. label Apr 24, 2026
Copy link
Copy Markdown
Contributor

@tgross35 tgross35 Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Using a random file for a thread)

The rust implementation requires that the caller and callee agree on the exact type. This is slightly more conservative than C, which does allow e.g. conversion between signed and unsigned integers, or to/from void pointers.

I wonder, is this too strict? And does this restriction apply across FFI?

I'm thinking about a printf implementation where it is common and valid (as far as I know) to use %x with both signed and unsigned integers.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is too strict, and the reference PR does not state it this strongly.

There is a difference here between what C says (in section 7.16.1.1):

If type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases:

  • both types are pointers to qualified or unqualified versions of compatible types;
  • one type is compatible with a signed integer type, the other type is compatible with the
    corresponding unsigned integer type, and the value is representable in both types;
  • one type is pointer to qualified or unqualified void and the other is a pointer to a qualified or
    unqualified character type;
  • or, the type of the next argument is nullptr_t and type is a pointer type that has the same representation and alignment requirements as a pointer to a character type

So signedness is irrelevant, and my interpretation of the other rules is that any pointer type is compatible with any other pointer type (in the same address space).

That is not what Miri currently implements, it instead uses strict type equality because @RalfJung did not have much appetite for (from memory, the third, but in any case) another notion of type equivalence. I went with the more restrictive formulation because we can always relax it later, the inverse is not true.

Perhaps T-opsem has suggestions for a better way to phrase this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we can relax the signedness requirements to match C then I think that is preferable, so we don't need to worry about soundness across FFI. It makes sense that Miri should match up with whatever behavior is decided, pinging @rust-lang/miri for thoughts here.

Is "compatible types" in the context of varargs defined anywhere? My read is that if they mean va-compatible then you could consider int * and unsigned * to be compatible, and you could consider void * and char * to be compatible, but you couldn't consider int * and long * to be compatible. Though I don't think Ive ever seen anybody cast pointers to void before using %p.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it does mean va-compatible then unsafe impl<T> VaArgSafe for *mut T {} may not be correct

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "compatible types" in the context of varargs defined anywhere?

Hmm no, I think your read is correct.

If it does mean va-compatible then unsafe impl<T> VaArgSafe for *mut T {} may not be correct

Based on the last rule I think *mut T always

has the same representation and alignment requirements as a pointer to a character type

but then the only valid value you can provide there is the NULL pointer... So then unsafe impl<T: VaArgSafe> VaArgSafe for *mut T {} might be more accurate.


That's really cumbersome, and I've similarly never seen people cast to void * when using %p. I don't really see a practical reason for it either.

Copy link
Copy Markdown
Contributor

@tgross35 tgross35 Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.gnu.org/software/c-intro-and-ref/manual/html_node/Compatible-Types.html spells out compatible types in a bit clearer form. edit In particular:

In C, two different primitive types are never compatible

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha! In 7.21.6.

p The argument shall be a pointer to void.

So the common use in C is UB. Nice.

Found via https://stackoverflow.com/questions/24867814/printfp-and-casting-to-void

Comment on lines -26 to -38
#[unstable(
feature = "c_variadic",
issue = "44930",
reason = "the `c_variadic` feature has not been properly tested on all supported platforms"
)]
mod va_list;
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
pub use self::va_list::{VaArgSafe, VaList};

#[unstable(
feature = "c_variadic",
issue = "44930",
reason = "the `c_variadic` feature has not been properly tested on all supported platforms"
)]
pub mod va_list;
Copy link
Copy Markdown
Contributor

@tgross35 tgross35 Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to do any surface area change like the module in a separate PR so we see the effects in the nightly docs

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I only noticed that the module was pub when making this PR. T-libs-api decided to not make the module pub but just export the type and trait from core::ffi.

//@ check-pass

#![feature(c_variadic)]
#![feature(custom_inner_attributes)]
Copy link
Copy Markdown
Contributor

@tgross35 tgross35 Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any idea why custom_inner_attributes wasn't needed before?

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's needed for top-level #![rustfmt::skip], there is an in-flight PR I believe to accept this (or anyway this got talked about recently)

The file is sensitive to formatting (the formatter deletes the attributes in some places), hence the rustfmt::skip.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah so not directly related to the removal here. Makes sense, probably just worth a note

@folkertdev folkertdev force-pushed the stabilize-c-variadic branch from 50f6221 to 5edeb59 Compare April 24, 2026 20:27
@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented Apr 25, 2026

☔ The latest upstream changes (presumably #155755) made this pull request unmergeable. Please resolve the merge conflicts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-run-make Area: port run-make Makefiles to rmake.rs F-c_variadic `#![feature(c_variadic)]` F-explicit_tail_calls `#![feature(explicit_tail_calls)]` I-lang-nominated Nominated for discussion during a lang team meeting. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. PG-exploit-mitigations Project group: Exploit mitigations S-waiting-on-t-lang Status: Awaiting decision from T-lang T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants