Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions compiler/rustc_ast_passes/src/feature_gate.rs
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

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor};
use rustc_ast::visit::{self, AssocCtxt, FnKind, Visitor};
use rustc_ast::{self as ast, AttrVec, GenericBound, NodeId, PatKind, attr, token};
use rustc_attr_parsing::AttributeParser;
use rustc_errors::msg;
Expand Down Expand Up @@ -388,7 +388,7 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
visit::walk_poly_trait_ref(self, t);
}

fn visit_fn(&mut self, fn_kind: FnKind<'a>, _: &AttrVec, span: Span, _: NodeId) {
fn visit_fn(&mut self, fn_kind: FnKind<'a>, _: &AttrVec, _: Span, _: NodeId) {
if let Some(_header) = fn_kind.header() {
// Stability of const fn methods are covered in `visit_assoc_item` below.
}
Expand All @@ -397,10 +397,6 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
self.check_late_bound_lifetime_defs(generic_params);
}

if fn_kind.ctxt() != Some(FnCtxt::Foreign) && fn_kind.decl().c_variadic() {
gate!(self, c_variadic, span, "C-variadic functions are unstable");
}

visit::walk_fn(self, fn_kind)
}

Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/accepted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ declare_features! (
(accepted, c_str_literals, "1.77.0", Some(105723)),
/// Allows `extern "C-unwind" fn` to enable unwinding across ABI boundaries and treat `extern "C" fn` as nounwind.
(accepted, c_unwind, "1.81.0", Some(74990)),
/// Allows using C-variadics.
(accepted, c_variadic, "CURRENT_RUSTC_VERSION", Some(44930)),
/// Allows `#[cfg_attr(predicate, multiple, attributes, here)]`.
(accepted, cfg_attr_multi, "1.33.0", Some(54881)),
/// Allows the use of `#[cfg(<true/false>)]`.
Expand Down
2 changes: 0 additions & 2 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,6 @@ declare_features! (
(unstable, avx10_target_feature, "1.88.0", Some(138843)),
/// Target features on bpf.
(unstable, bpf_target_feature, "1.54.0", Some(150247)),
/// Allows using C-variadics.
(unstable, c_variadic, "1.34.0", Some(44930)),
/// Allows defining c-variadic naked functions with any extern ABI that is allowed
/// on c-variadic foreign functions.
(unstable, c_variadic_naked_functions, "1.93.0", Some(148767)),
Expand Down
14 changes: 2 additions & 12 deletions library/core/src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,10 @@ use crate::fmt;
#[stable(feature = "c_str_module", since = "1.88.0")]
pub mod c_str;

#[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;
Comment on lines -26 to -38
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.


mod primitives;
#[stable(feature = "core_ffi_c", since = "1.64.0")]
pub use self::primitives::{
Expand Down
21 changes: 19 additions & 2 deletions library/core/src/ffi/va_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,6 @@ crate::cfg_select! {
/// is automatically initialized (equivalent to calling `va_start` in C).
///
/// ```
/// #![feature(c_variadic)]
///
/// use std::ffi::VaList;
///
/// /// # Safety
Expand Down Expand Up @@ -224,11 +222,13 @@ crate::cfg_select! {
/// terms of layout and ABI.
#[repr(transparent)]
#[lang = "va_list"]
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
pub struct VaList<'a> {
inner: VaListInner,
_marker: PhantomCovariantLifetime<'a>,
}

#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
impl fmt::Debug for VaList<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// No need to include `_marker` in debug output.
Expand All @@ -243,6 +243,7 @@ impl VaList<'_> {
}
}

#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
impl<'f> const Clone for VaList<'f> {
#[inline] // Avoid codegen when not used to help backends that don't support VaList.
Expand All @@ -255,6 +256,7 @@ impl<'f> const Clone for VaList<'f> {
}
}

#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
impl<'f> const Drop for VaList<'f> {
#[inline] // Avoid codegen when not used to help backends that don't support VaList.
Expand All @@ -264,6 +266,7 @@ impl<'f> const Drop for VaList<'f> {
}
}

#[unstable(feature = "c_variadic_internals", issue = "none")]
mod sealed {
pub trait Sealed {}

Expand Down Expand Up @@ -318,6 +321,7 @@ mod sealed {
// types with an alignment larger than 8, or with a non-scalar layout. Inline assembly can be used
// to accept unsupported types in the meantime.
#[lang = "va_arg_safe"]
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
pub unsafe trait VaArgSafe: sealed::Sealed {}

crate::cfg_select! {
Expand All @@ -326,7 +330,9 @@ crate::cfg_select! {
//
// - i8 is implicitly promoted to c_int in C, and cannot implement `VaArgSafe`.
// - u8 is implicitly promoted to c_uint in C, and cannot implement `VaArgSafe`.
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for i16 {}
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for u16 {}
}
_ => {
Expand All @@ -340,6 +346,7 @@ crate::cfg_select! {
crate::cfg_select! {
target_arch = "avr" => {
// c_double is f32 on this target.
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for f32 {}
}
_ => {
Expand All @@ -349,17 +356,26 @@ crate::cfg_select! {
}
}

#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for i32 {}
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for i64 {}
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for isize {}

#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for u32 {}
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for u64 {}
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for usize {}

#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl VaArgSafe for f64 {}

#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl<T> VaArgSafe for *mut T {}
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
unsafe impl<T> VaArgSafe for *const T {}

// Check that relevant `core::ffi` types implement `VaArgSafe`.
Expand Down Expand Up @@ -390,6 +406,7 @@ impl<'f> VaList<'f> {
/// Calling this function with an incompatible type, an invalid value, or when there
/// are no more variable arguments, is unsound.
#[inline] // Avoid codegen when not used to help backends that don't support VaList.
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
#[rustc_const_unstable(feature = "const_c_variadic", issue = "151787")]
pub const unsafe fn next_arg<T: VaArgSafe>(&mut self) -> T {
// SAFETY: the caller must uphold the safety contract for `va_arg`.
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
issue = "none"
)]

use crate::ffi::va_list::{VaArgSafe, VaList};
use crate::ffi::{VaArgSafe, VaList};
use crate::marker::{ConstParamTy, DiscriminantKind, PointeeSized, Tuple};
use crate::{mem, ptr};

Expand Down
7 changes: 1 addition & 6 deletions library/std/src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,7 @@ pub mod c_str;

#[stable(feature = "core_c_void", since = "1.30.0")]
pub use core::ffi::c_void;
#[unstable(
feature = "c_variadic",
reason = "the `c_variadic` feature has not been properly tested on \
all supported platforms",
issue = "44930"
)]
#[stable(feature = "c_variadic", since = "CURRENT_RUSTC_VERSION")]
pub use core::ffi::{VaArgSafe, VaList};
#[stable(feature = "core_ffi_c", since = "1.64.0")]
pub use core::ffi::{
Expand Down
1 change: 0 additions & 1 deletion library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,6 @@
// Only for re-exporting:
// tidy-alphabetical-start
#![feature(async_iterator)]
#![feature(c_variadic)]
#![feature(cfg_accessible)]
#![feature(cfg_eval)]
#![feature(concat_bytes)]
Expand Down
24 changes: 0 additions & 24 deletions src/doc/unstable-book/src/language-features/c-variadic.md

This file was deleted.

2 changes: 0 additions & 2 deletions src/tools/miri/tests/fail/c-variadic-mismatch-count.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![feature(c_variadic)]

unsafe extern "C" fn helper(_: i32, _: ...) {}

fn main() {
Expand Down
2 changes: 0 additions & 2 deletions src/tools/miri/tests/fail/c-variadic-mismatch.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![feature(c_variadic)]

unsafe extern "C" fn helper(_: i32, _: ...) {}

fn main() {
Expand Down
2 changes: 0 additions & 2 deletions src/tools/miri/tests/fail/c-variadic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![feature(c_variadic)]

//@error-in-other-file: Undefined Behavior: more C-variadic arguments read than were passed

fn read_too_many() {
Expand Down
1 change: 0 additions & 1 deletion src/tools/miri/tests/pass/c-variadic-ignored-argument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
//@ ignore-target: powerpc # does not ignore ZST arguments
//@ ignore-target: s390x # does not ignore ZST arguments
//@ ignore-target: sparc # does not ignore ZST arguments
#![feature(c_variadic)]

// Some platforms ignore ZSTs, meaning that the argument is not passed, even though it is part
// of the callee's ABI. Test that this doesn't trip any asserts.
Expand Down
2 changes: 0 additions & 2 deletions src/tools/miri/tests/pass/c-variadic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
#![feature(c_variadic)]

use std::ffi::{CStr, VaList, c_char, c_double, c_int, c_long};

fn ignores_arguments() {
Expand Down
1 change: 0 additions & 1 deletion tests/assembly-llvm/c-variadic/arm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
//@ ignore-android
#![no_std]
#![crate_type = "lib"]
#![feature(c_variadic)]

// Check that the assembly that rustc generates matches what clang emits. This example in particular
// is related to https://github.com/rust-lang/rust/pull/144549 and shows the effect of us correctly
Expand Down
2 changes: 1 addition & 1 deletion tests/assembly-llvm/c-variadic/mips.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//@ [MIPS64] needs-llvm-components: mips
//@ [MIPS64EL] compile-flags: -Copt-level=3 --target mips64el-unknown-linux-gnuabi64
//@ [MIPS64EL] needs-llvm-components: mips
#![feature(c_variadic, no_core, lang_items, intrinsics, rustc_attrs, asm_experimental_arch)]
#![feature(no_core, lang_items, intrinsics, rustc_attrs, asm_experimental_arch)]
#![no_core]
#![crate_type = "lib"]

Expand Down
2 changes: 1 addition & 1 deletion tests/assembly-llvm/c-variadic/sparc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//@ [SPARC] needs-llvm-components: sparc
//@ [SPARC64] compile-flags: -Copt-level=3 --target sparc64-unknown-linux-gnu
//@ [SPARC64] needs-llvm-components: sparc
#![feature(c_variadic, no_core, lang_items, intrinsics, rustc_attrs, asm_experimental_arch)]
#![feature(no_core, lang_items, intrinsics, rustc_attrs, asm_experimental_arch)]
#![no_core]
#![crate_type = "lib"]

Expand Down
1 change: 0 additions & 1 deletion tests/codegen-llvm/c-variadic-lifetime.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
//@ add-minicore
//@ compile-flags: -Copt-level=3
#![feature(c_variadic)]
#![crate_type = "lib"]

// Check that `%args` explicitly has its lifetime start and end. Being explicit can improve
Expand Down
1 change: 0 additions & 1 deletion tests/codegen-llvm/cffi/c-variadic-inline.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//@ compile-flags: -C opt-level=3
#![feature(c_variadic)]

// Test that the inline attributes are accepted on C-variadic functions.
//
Expand Down
1 change: 0 additions & 1 deletion tests/codegen-llvm/cffi/c-variadic-naked.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// tests that `va_start` is not injected into naked functions

#![crate_type = "lib"]
#![feature(c_variadic)]
#![no_std]

#[unsafe(naked)]
Expand Down
1 change: 0 additions & 1 deletion tests/codegen-llvm/cffi/c-variadic-opt.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//@ compile-flags: -C opt-level=3

#![crate_type = "lib"]
#![feature(c_variadic)]
#![no_std]
use core::ffi::VaList;

Expand Down
1 change: 0 additions & 1 deletion tests/codegen-llvm/cffi/c-variadic-va_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
//@ compile-flags: -Copt-level=3

#![crate_type = "lib"]
#![feature(c_variadic)]
#![no_std]
use core::ffi::VaList;

Expand Down
1 change: 0 additions & 1 deletion tests/codegen-llvm/cffi/c-variadic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
//@ compile-flags: -C no-prepopulate-passes -Copt-level=0

#![crate_type = "lib"]
#![feature(c_variadic)]
#![no_std]
use core::ffi::VaList;

Expand Down
1 change: 0 additions & 1 deletion tests/mir-opt/inline/inline_compatibility.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

#![crate_type = "lib"]
#![feature(sanitize)]
#![feature(c_variadic)]

#[inline]
#[target_feature(enable = "sse2")]
Expand Down
1 change: 0 additions & 1 deletion tests/pretty/fn-variadic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// See issue #58853.

//@ pp-exact
#![feature(c_variadic)]

extern "C" {
pub fn foo(x: i32, ...);
Expand Down
2 changes: 0 additions & 2 deletions tests/pretty/hir-fn-variadic.pp
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
#![attr = Feature([c_variadic#0])]
extern crate std;
#[attr = PreludeImport]
use ::std::prelude::rust_2015::*;
//@ pretty-compare-only
//@ pretty-mode:hir
//@ pp-exact:hir-fn-variadic.pp


extern "C" {
unsafe fn foo(x: i32, va1: ...);
}
Expand Down
2 changes: 0 additions & 2 deletions tests/pretty/hir-fn-variadic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
//@ pretty-mode:hir
//@ pp-exact:hir-fn-variadic.pp

#![feature(c_variadic)]

extern "C" {
pub fn foo(x: i32, va1: ...);
}
Expand Down
1 change: 0 additions & 1 deletion tests/run-make/c-link-to-rust-va-list-fn/checkrust.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#![crate_type = "staticlib"]
#![feature(c_variadic)]

use core::ffi::{CStr, VaList, c_char, c_double, c_int, c_long, c_longlong};

Expand Down
1 change: 0 additions & 1 deletion tests/ui-fulldeps/rustc_public/check_abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,6 @@ fn generate_input(path: &str) -> std::io::Result<()> {
write!(
file,
r#"
#![feature(c_variadic)]
#![allow(unused_variables)]
use std::num::NonZero;
Expand Down
2 changes: 0 additions & 2 deletions tests/ui/abi/variadic-ffi.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
//@ run-pass
//@ ignore-backends: gcc

#![feature(c_variadic)]

use std::ffi::VaList;

#[link(name = "rust_test_helpers", kind = "static")]
Expand Down
Loading
Loading