Skip to content

Add FCW for invalid C variadic arguments - #162478

Open
theemathas wants to merge 3 commits into
rust-lang:mainfrom
theemathas:variadic-fcw
Open

theemathas wants to merge 3 commits into
rust-lang:mainfrom
theemathas:variadic-fcw

Conversation

@theemathas

@theemathas theemathas commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

View all comments

Fixes #61275 by adding an FCW for invalid C-variadic arguments: invalid_c_variadic_arguments.

Tracking issue for the FCW: #162483

For the purposes of this FCW, a valid C-variadic argument must either implement VaArgSafe, or be a thin reference.

cc @RalfJung


Background

C-variadic functions are functions defined either in Rust or externally via FFI that can accept any number of arguments. However, due to C ABI weirdness, only certain types can be passed as C-variadic arguments.

Previously, we had a check that would cause us to attempt to emit a hard error on commonly mistakenly used C-variadic argument types. In particular, this affected types that are not "directly supported" as a variadic argument, but would be automatically coerced to a supported type in C/C++. For instance, when passing a short, a C/C++ compiler will automatically promote this to int and so on the ABI level, an int gets passed. We do not do such coercions in Rust, so passing an i16 can lead to fatal bugs due to the wrong ABI being used.

In #61275, it was found that this hard error didn't prevent such footguns from occurring when the C-variadic function was called with generic arguments. It was then also later found that this error had a bug that caused it to depend on the details of the type inference algorithm.

The current behavior of this hard error is as follows:

  • The check runs during the process of doing type inference. We compute the type of the argument, based on the information so far. If we don't yet know the concrete type, we stop and don't emit an error.
  • If the argument is a function item type (as opposed to a function pointer type), we emit an error.
  • If the argument is of type f32, i8, i16, u8, u16, or bool, and the type doesn't implement VaArgSafe in the current target, we emit an error.
  • Otherwise, we don't emit an error. (Notably, passing a random type like String doesn't trigger this error.)

In #155697, we stabilized the ability to define C-variadic functions in Rust. With it, we also stabilized the VaArgSafe trait. This trait is implemented for types that are supported as variadic arguments. Thus, we now have the ability, in stable Rust, to describe the type requirements for being supported as a C-variadic argument.

Therefore, this PR adds an FCW that would warn against C-variadic arguments that are not VaArgSafe. In generic contexts, users can add a T: VaArgSafe bound to satisfy this lint. This FCW runs after type inference is done, but before monomorphization.

There's a caveat though: There's likely much code in the wild that passes a reference as a C-variadic argument. However, references do not implement VaArgSafe yet. Thus, we don't yet lint when a thin reference is passed as a C-variadic argument.

In the future, I expect that it would be possible to turn this into a hard error by, in the type inference/checking algorithm, adding a trait obligation that requires C-variadic arguments to implement VaArgSafe. (This is similar to what happens when one calls a fn<T: VaArgSafe>(T).) This can technically break code that wasn't previously linted, due to lifetime-dependent where bounds, and maybe due to the effect that the trait bound has on subsequent type inference. I expect this to be extremely unlikely though.

@theemathas theemathas added T-lang Relevant to the language team T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. labels Sep 8, 2026
@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Sep 8, 2026
@rustbot

rustbot commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

r? @dingxiangfei2009

rustbot has assigned @dingxiangfei2009.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler
  • compiler expanded to 75 candidates
  • Random selection from 19 candidates

Comment thread compiler/rustc_lint/src/builtin.rs
Comment thread compiler/rustc_lint/src/builtin.rs Outdated
Comment on lines +3232 to +3234
@future_incompatible = FutureIncompatibleInfo {
reason: fcw!(FutureReleaseError #61275),
};

@RalfJung RalfJung Sep 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah, people changed the syntax of this macro again and now one cannot easily tell whether this will be reported in dependencies or not. :/

That's a side-effect of #141936. @WaffleLapkin why is report_in_depds an optional field? The default is far from obvious. (When I introduced FutureReleaseErrorDontReportInDeps many people were surprised that FCW do not report-in-deps by default. That's why I introduced this name that makes it so obvious. IMO it is a step backwards that now we again have syntax where this is not obvious.)

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.

Based on this, it seems that the field being optional is intentional:

/// If set to `true`, this will make future incompatibility warnings show up in cargo's
/// reports.
///
/// When a future incompatibility warning is first inroduced, set this to `false`
/// (or, rather, don't override the default). This allows crate developers an opportunity
/// to fix the warning before blasting all dependents with a warning they can't fix
/// (dependents have to wait for a new release of the affected crate to be published).
///
/// After a lint has been in this state for a while, consider setting this to true, so it
/// warns for everyone. It is a good signal that it is ready if you can determine that all
/// or most affected crates on crates.io have been updated.
pub report_in_deps: bool,

@RalfJung RalfJung Sep 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think that's a bad choice. It certainly could have warranted a bit more discussion, given that this effectively reverted changes I made previously (#116049), in terms of what is and is not explicit in the API.

I guess people weren't aware of the prior discussion and didn't realize the downsides of the new API choice. Time to make another PR to make report_in_depds mandatory I guess... except I don't know how to make it mandatory just for FutureReleaseError; we don't need it mandatory for edition errors as those "obviously" are not reported in dependencies. That's the downside of the new structure...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was indeed not aware of the previous discussion, ugh =_=

I think the justification from #141936, "It gets especially unruly if you want to add non-FutureReleaseError* warnings which are included in the reports." was targeted at EditionAndFutureReleaseError which I was working with at the time, in the process of stabilizing never.

Looking at the current structure, I'd say we can put report_in_deps in ReleaseFcw. That adds the assumption that we only want to report warnings in dependencies if we plan to change something in a future release, but I guess that's fine (and is at the very least currently true).

I'll make a PR for this.

@RalfJung RalfJung Sep 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looking at the current structure, I'd say we can put report_in_deps in ReleaseFcw. That adds the assumption that we only want to report warnings in dependencies if we plan to change something in a future release, but I guess that's fine (and is at the very least currently true).

I was assuming you'd not want that since it seems to partially revert your PR #141936, by coupling report_in_deps with the reason again. But it sounds great to me so if you can also live with it, all good. :)

I'll make a PR for 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.

Presumably we'd then add ReleaseFcw to EditionAndFutureReleaseError and to EditionAndFutureReleaseSemanticsChange? (Currently these only include EditionFcw.)

If helpful to factoring, note that lang has been following the policy of setting report_in_deps = true exactly when we make an FCW deny-by-default (and otherwise setting report_in_deps = false). Possibly, after cleaning up any lingering exceptions, it could be OK to lean on that.

@RalfJung RalfJung Sep 9, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

#159700 is a recent example of a Warn + report_in_deps lint. It also shows up in a lot of dependency trees so maybe that was not a good call and it should have been Warn-only for a while like normal FCWs...

@rust-log-analyzer

This comment has been minimized.

Comment thread compiler/rustc_lint/src/builtin.rs
@beetrees

beetrees commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

It would be good to do a crater check run with the lint level set to deny to see if there are any commonly-used types that don't currently implement VaArgSafe.

@RalfJung

RalfJung commented Sep 8, 2026

Copy link
Copy Markdown
Member

Deny runs aren't actually that great as the error will be ignored in build-deps. Ideally we crater a version of this where we make this a hard error. Sadly that requires more patching of the code. :/

@rust-log-analyzer

This comment has been minimized.

@rustbot

rustbot commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

miri is developed in its own repository. If the Miri part of this change can be broken out, consider making this change to rust-lang/miri instead. However, if Miri needs adjusting for rustc changes, just ignore this message.

cc @rust-lang/miri

@theemathas

This comment was marked as outdated.

@rust-bors

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Sep 8, 2026
Add FCW for invalid C variadic arguments
@rust-log-analyzer

This comment has been minimized.

@RalfJung RalfJung added the I-lang-nominated Nominated for discussion during a lang team meeting. label Sep 8, 2026
@RalfJung

RalfJung commented Sep 8, 2026

Copy link
Copy Markdown
Member

@rust-lang/lang Nominating for team discussion. :) See the PR description for a summary. We ask for your feedback on which of these three options you would prefer (or whether you'd prefer something entirely different):

  1. Trigger the lint on all !VaArgSafe types. Note that even the standard library triggers the lint then, though so far only one place where that happens was discovered.
  2. Trigger the lint on all !VaArgSafe types, except thin references.
  3. Trigger the lint on all !VaArgSafe types, and make thin references VaArgSafe.

My personal preference is option 3. That standard library code looks entirely reasonable, there is no reason to change it. We define &mut as being ABI-compatible with *mut, so it is odd to make a distinction between them here. That said, making the type VaArgSafe makes it possible to get a reference out of anext_arg, which is obviously very unsafe and the lifetime is entirely unconstrained -- so maybe there is a point to be made for being asymmetric wrt. what we accept on the caller vs callee side.

@rust-log-analyzer

This comment has been minimized.

@rust-bors rust-bors Bot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 8, 2026
@rust-bors

rust-bors Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

💔 Test for ec4987a failed: CI. Failed jobs:

@traviscross traviscross added the I-lang-radar Items that are on lang's radar and will need eventual work or consideration. label Sep 10, 2026
@Jules-Bertholet

Jules-Bertholet commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

I'm against making this a FCW; I think it should just be a normal warn-by-default lint (perhaps deny for certain cases). There are perfectly legitimate reasons to pass non-VaArgSafe types as variadic arguments (e.g. repr(C)structs). I think it's good to be conservative when adding a new feature, so I'm fine with VaArgSafe being as restrictive as it currently is for the situations where it's currently required. But a breaking change should require much stronger justification.

@theemathas

Copy link
Copy Markdown
Contributor Author

There are perfectly legitimate reasons to pass non-VaArgSafe types as generics (e.g. repr(C) structs).

@Jules-Bertholet Is there a legitimate reason to then proceed to pass a value of that type as a variadic argument?

@Jules-Bertholet

Copy link
Copy Markdown
Contributor

If some weird C API you are linking against requires it, then that's what you have to do. (Sorry for typo, see edited message)

@theemathas

Copy link
Copy Markdown
Contributor Author

@Jules-Bertholet I believe that passing a repr(C) struct (or a struct defined in C) as a variadic argument is UB both in C and in rust. Or if it's not UB when you pass it, it will be UB when you try to read it.

@Jules-Bertholet

Copy link
Copy Markdown
Contributor

As far as I am aware, no such UB exists in the C standard (1, 2).

@beetrees

Copy link
Copy Markdown
Contributor

I believe that passing a repr(C) struct (or a struct defined in C) as a variadic argument is UB both in C and in rust. Or if it's not UB when you pass it, it will be UB when you try to read it.

In C, all types that can be passed as function arguments (except for types like short and float that get promoted) can be passed as varargs.

There are perfectly legitimate reasons to pass non-VaArgSafe types as variadic arguments (e.g. repr(C)structs).

I think the solution there would be to allow such types to implement VaArgSafe (of course, this requires a bunch of design work). Hopefully the crater run should help determine whether this needs to occur before adding this FCW - the FCW could also temporarily ignore all #[repr(C)] structs etc. while that design work occurs.

@craterbot

Copy link
Copy Markdown
Collaborator

🚧 Experiment pr-162478 is now running

ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

@craterbot

Copy link
Copy Markdown
Collaborator

🎉 Experiment pr-162478 is completed!
📊 27578 regressed and 2 fixed (1107266 total)
📊 5825 spurious results on the retry-regressed-list.txt, consider a retry1 if this is a significant amount.
📰 Open the summary report.

⚠️ If you notice any spurious failure please add them to the denylist!
ℹ️ Crater is a tool to run experiments across parts of the Rust ecosystem. Learn more

Footnotes

  1. re-run the experiment with crates=https://crater-reports.s3.amazonaws.com/pr-162478/retry-regressed-list.txt

@craterbot craterbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-crater Status: Waiting on a crater run to be completed. labels Sep 16, 2026
@RalfJung

RalfJung commented Sep 16, 2026

Copy link
Copy Markdown
Member

27578 regressed

I think that may be a new record 😂
The bulk of this seems to be nix, which we already knew before running crater as it broke the compiler itself.

@beetrees

beetrees commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

I did a quick rg to find the raw list of types that appear in the regressed error messages. There are a surprising number of unsized pointers (slices, &CStr and &str) getting passed directly to variadic functions despite having no C-compatible ABI (I imagine that the intention was for .as_ptr() to be passed).

The full list
()
A
<A as FormatArg>::Raw
<A as PrintfArgument>::CPrintfType
AtFlags
Axis
AxisSource
B
<B as FormatArg>::Raw
<B as PrintfArgument>::CPrintfType
BorrowedFd<'_>
bpf_cmd
BpfCmd
ButtonState
C
Capability
<C as FormatArg>::Raw
<C as PrintfArgument>::CPrintfType
char
*const CStr
&CStr
CString
<D as FormatArg>::Raw
<D as PrintfArgument>::CPrintfType
<E as FormatArg>::Raw
<E as PrintfArgument>::CPrintfType
enums::VipsAccess
enums::VipsFailOn
enums::VipsForeignFlags
enums::VipsHeifCompression
enums::VipsHeifEncoder
enums::VipsIntent
enums::VipsInteresting
enums::VipsInterpretation
enums::VipsKeep
enums::VipsPCS
enums::VipsPngFilter
enums::VipsSize
enums::VipsSubsample
enums::VipsWebpPreset
Errno
extern "C" fn(*const c_void, u32, *mut c_void) -> i32
extern "C" fn(*const c_void, u32) -> u32
extern "C" fn(*mut Box<dyn FnMut(usize, usize, usize, usize)>, f64, f64, f64, f64) -> i32
extern "C" fn(*mut CURL, u32, *mut c_void)
extern "C" fn(*mut CURL, u32, u32, *mut c_void)
extern "C" fn(*mut c_void, i32, *const i8)
extern "C" fn(*mut F)
extern "C" fn(*mut i8, usize, usize, *mut c_void) -> usize
extern "C" fn(*mut u8, usize, usize, *mut Body<'_>) -> usize
extern "C" fn(*mut u8, usize, usize, &mut ResponseBuilder) -> usize
extern "C" fn(*mut u8, usize, usize, *mut ResponseBuilder) -> usize
<F as FormatArg>::Raw
<F as PrintfArgument>::CPrintfType
fcntl::FdFlags
Fd1
FdFlags
for<'a> unsafe fn(&'a Inner)
for<'a> unsafe fn(*const c_void, i32, &'a Inner)
for<'a> unsafe fn(i32, &'a Inner)
for<'a> unsafe fn(u32, *const c_void, &'a Inner)
for<'a> unsafe fn(u32, *mut c_void, &'a Inner)
Format
FsConfig
FullscreenMethod
<G as FormatArg>::Raw
<G as PrintfArgument>::CPrintfType
<H as FormatArg>::Raw
<H as PrintfArgument>::CPrintfType
<I as FormatArg>::Raw
InsnX86
<J as FormatArg>::Raw
<K as FormatArg>::Raw
KdMode
KeymapFormat
KeyState
KvmVmType
<L as FormatArg>::Raw
linux_bindings_x86_64::bpf_cmd
<M as FormatArg>::Raw
MediaDirection
MIR_op_t
mpk::PkeyAccessRights
*mut input_mt_request_layout
&mut [MaybeUninit<u8>]
&mut Self
&mut T
&mut [u8]
&mut [watch_device::{closure#0}::{closure#0}::input_absinfo]
<N as FormatArg>::Raw
NonZero<usize>
<O as FormatArg>::Raw
OfferAnswerParameters
Option<&[u8]>
Option<unsafe extern "C" fn(*mut Curl, i32, i32, *mut c_void, *mut c_void) -> i32>
Option<unsafe extern "C" fn(*mut CurlMulti, i64, *mut c_void) -> i32>
<P as FormatArg>::Raw
Pid
PrintkArg
<Q as FormatArg>::Raw
<R as FormatArg>::Raw
rd_kafka_vtype_t
Resize
Result<CString, NulError>
ruby_special_consts
rustix::process::Pid
<S as FormatArg>::Raw
scmp_arg_cmp
SCMP_ARG_CMP
scmp::scmp_arg_cmp
&[Segment<'_, '_>]
Self
semun
Signal
&[spi_ioc_transfer<'_, '_>]
&std::ffi::CStr
&str
String
Subpixel
sys::linux::kvm::KvmCap
sys::linux::kvm::KvmVmType
sys::linux::vfio::VfioIommu
T
T0Jni
T10Jni
T11Jni
T1Jni
T2Jni
T3Jni
T4Jni
T5Jni
T6Jni
T7Jni
T8Jni
T9Jni
<T as FormatArg>::Raw
<T as traits::CReprHolder>::Output
&[TransferSegment<'_, '_>]
Transform
Transient
TTF_HorizontalAlignment
TunFeature
types::DefaultKeyring
&[u8]
[u8; 16]
<U as FormatArg>::Raw
unicorn_const::ContextMode
unicorn_engine_sys::Arm64Insn
unicorn_engine_sys::ContextMode
unicorn_engine_sys::X86Insn
unsafe extern "C" fn(*mut c_void)
unsafe extern "C" fn(*mut i8, usize, usize, *mut c_void) -> usize
VaList<'_>
VALUE
<V as FormatArg>::Raw
VipsCombineMode
VipsKernel
VipsSize
vp8e_token_partitions
vp8e_tuning
Vp8NoiseSensitivity
Vp8ScreenContentMode
Vp9AQMode
Vp9ColorRange
Vp9NoiseSensitivity
Vp9SvcInterLayerPred
Vp9TuneContent
<W as FormatArg>::Raw
wl_output::Mode
wl_shell_surface::Resize
wl_shell_surface::Transient
x86_const::InsnSysX86
x86::InsnSysX86
x86::InsnX86
<X as FormatArg>::Raw
<Y as FormatArg>::Raw
<Z as FormatArg>::Raw

@theemathas

Copy link
Copy Markdown
Contributor Author

I'm listing what types triggered the lint in crates with a total 100 or more dependents (total count from all versions of the crate combined, but I'm only taking the types from the latest version that was flagged).

  • nix (26273 dependents)
    • NonZero<usize>
    • AtFlags (a repr(transparent) struct around a c_int)
  • miniquad (2303 dependents)
    • ()
  • rppal (470 dependents)
    • &[Segment<'_, '_>]
  • rdkafka (338 dependents)
    • rd_kafka_vtype_t (a repr(u32) field-less enum)
  • wayland-client (268 dependents)
    • Format (a repr(u32) field-less enum)
    • Transient (some struct generated by a macro, haven't checked yet what it actually is)
  • perf-event-open-sys (204 dependents)
    • Generic type parameter A
  • gilrs-core (203 dependents)
    • &mut [MaybeUninit<u8>]
    • &mut [u8]
  • libsql-rusqlite (133 dependents)
    • extern "C" fn(*mut c_void, i32, *const i8)

@theemathas

Copy link
Copy Markdown
Contributor Author

How do we proceed now?

@folkertdev

Copy link
Copy Markdown
Contributor

The full list
...
VaList<'_>

Of course you want a VaList in your VaList

How do we proceed now?

Passing function pointers, NonZero and int-repr enums is kind of reasonable. Just because rust cannot (currently) read them back out of a VaList doesn't mean that it can't work.

Maybe we can start with linting on fat pointers? I imagine that can have a good suggestion too with something like "did you mean foo.as_{mut}_ptr().

Possibly this can be extended to anything that improper_ctypes lints on. Normally we report that lint on the definition, but in this case the definition does not specify the type so doing it at the call site seems like an OK option.

So, I guess what I'm proposing is to tighten the lint gradually?

@theemathas theemathas added the I-lang-nominated Nominated for discussion during a lang team meeting. label Sep 16, 2026
@beetrees

Copy link
Copy Markdown
Contributor

The gilrs-core is case is actually from the ioctl_read_buf macro from nix: this was fixed in nix-rust/nix#2181 so only dependants Cargo.locked to older versions of nix are affected.

@traviscross traviscross added the P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang label Sep 16, 2026
@tmandry

tmandry commented Sep 16, 2026

Copy link
Copy Markdown
Member

We discussed this in the lang meeting today. Those present weren't sure an FCW made sense while there are types that should implement this but do not (defined by the standard library), see below. My belief is that we shouldn't recommend #[allow]-ing an FCW ever.

The suggestion in #162478 (comment) to make thin references implement VaArgSafe made sense to us. We also think NonZero and NonNull ought to implement it. @clarfonthey opened #162858 for this.

A question was raised about extern types, which are currently unstable: These types do not implement Sized, but &T and *mut T can still be thin. There doesn't seem to be a way to express this in the implementation bound, even with the unstable sized hierarchy. We can conceivably punt on this until extern types are stabilized, but it seems like users of that feature would have no choice but to ignore the FCW. Does that sound right, and what would a workable answer look like here? Do we need a PointerSized trait?

Function pointers are another class of types that do not implement the trait but should; see #153646.

Finally, in the meeting we discussed the question of whether ecosystem crates should be able to implement VaArgSafe. I think we decided this on #44930 for the time being, and that the answer is no for two one reason:

  1. Even with #[repr(transparent)] we do not guarantee ABI compatibility (as opposed to layout compatibility) for a wrapped type.
  2. VaArgSafe defines more than just ABI compatibility; it defines the mechanical interaction with the VaArgs API itself, as mentioned in Tracking issue for RFC 2137: Support defining C-compatible variadic functions in Rust (c_variadic) #44930 (comment).

Only the standard library can make these kinds of guarantees today. It's conceivable that we could open things up in the future, but for now I think we should continue to say that users can't define types that are VarArgSafe.

@Jules-Bertholet

Copy link
Copy Markdown
Contributor

Even with #[repr(transparent)] we do not guarantee ABI compatibility (as opposed to layout compatibility) for a wrapped type.

Yes we do.

@theemathas

theemathas commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

A question was raised about extern types, which are currently unstable: These types do not implement Sized, but &T and *mut T can still be thin. There doesn't seem to be a way to express this in the implementation bound, even with the unstable sized hierarchy.

Expressing this as a bound is actually quite trivial:

impl<T: PointeeSized + core::marker::Pointee<Metadata = ()>> VaArgSafe for *mut T

It might conflict with a blanket impl for FnPtr though.

@theemathas

theemathas commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

The biggest problem with disallowing external libraries from implementing VaArgSafe is that this prevents people from passing repr(transparent) wrappers around VaArgSafe types. Notably, the nix crate does this with the AtFlags type.

As proposed in #44930 (comment), we could allow libraries to implement this trait, but only with a check by the compiler to ensure that the impl is actually legitimate.

@lcnr

lcnr commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

A question was raised about extern types, which are currently unstable: These types do not implement Sized, but &T and *mut T can still be thin. There doesn't seem to be a way to express this in the implementation bound, even with the unstable sized hierarchy.

Expressing this as a bound is actually quite trivial:

impl<T: PointeeSized + core::marker::Pointee<Metadata = ()>> VaArgSafe for *mut T

It might conflict with a blanket impl for FnPtr though.

if the trait is unstable, you can make it a #[marker] trait 🤔 though cc T-types if you do so

@tgross35

Copy link
Copy Markdown
Member
1. Trigger the lint on all `!VaArgSafe` types.  Note that even the standard library [triggers the lint](https://github.com/rust-lang/rust/blob/745de6eca673de5329ec68f2689629a5ca45ab35/library/std/src/sys/net/connection/socket/unix.rs#L597-L598) then, though so far only one place where that happens was discovered.
2. Trigger the lint on all `!VaArgSafe` types, except thin references.
3. Trigger the lint on all `!VaArgSafe` types, and make thin references `VaArgSafe`.

Musing: I wonder if references should be not VaArgSafe, and there should be a lint that fires on references coerced to pointers for variadic calls that doesn't fire for separately-constructed raw pointers. It's pretty easy to wind up switching a T to &T in code, like changing from for x in v to for x in &v, which typically either continues to compile and run with no behavior difference (autoref / coercion) or shows up with build errors. Here, however, if a type passed to a variadic function gets switched from a u8 to &u8, it will continue to compile but quietly become completely unsound.

@RalfJung

Copy link
Copy Markdown
Member

The trait is stable... well it will be very soon.
Should we de-stabilize it for Rust 1.99 to give us time to think?

@asquared31415

asquared31415 commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

That is being discussed on zulip, because there's other reasons we might want to destabilize it: https://rust-lang.zulipchat.com/#narrow/channel/219381-t-libs/topic/Implementing.20.60VaArgSafe.60.20on.20references.20is.20a.20breaking.20change/with/624988218

@theemathas

Copy link
Copy Markdown
Contributor Author

As per #t-libs > Implementing `VaArgSafe` on references is a breaking change, we're very likely to destabilize VaArgSafe.

This branch has not been deployed

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

Labels

I-lang-nominated Nominated for discussion during a lang team meeting. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. P-lang-drag-1 Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Varargs are completely unchecked if passed as generics