SE-0495: C-compatible functions and enums

Hey all,

The review of SE-0495 "C compatible functions and enums" begins now and runs through October 9, 2025.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to the review manager via the forum messaging feature. When contacting the review manager directly, please keep the proposal link at the top of the message.

Try it out

Development toolchains built from main have this feature available when the experimental feature flags CDecl and CImplementation are specified. ObjC-compatible global functions are supported on main with the @_cdecl attribute.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at https://github.com/swiftlang/swift-evolution/blob/main/process.md .

Thanks for contributing to Swift!

Steve Canon
Review Manager

18 Likes

Acknowledging the very good reasons why structs are not part of this proposal, could it be useful and reasonable as part of the initial feature to support the special case of (frozen or non-evolution) structs with a single stored property itself representable in C?

We have elsewhere made the special guarantee that such structs are layout-compatible with their only stored property and therefore the currently idiomatic way to create newtype wrappers. Not sure if these have particular currency in C-interop use cases but perhaps?

3 Likes

Unfortunately it seems to me like explicitly C-layout structs would be a situation where we can't make this guarantee, since in many C ABIs, struct { some_t x; } and some_t are not entirely ABI-compatible, and I would expect a @c struct in Swift to follow the C conventions, at least when calling C functions.

6 Likes

This is an important feature to start gradually displacing code in C code bases with Swift, so I’m +1 almost no matter what. I do have two questions still:

In Type Checking, I’m noticing that we do bridge @convention(c) ā€œclosuresā€ to C function types, but there’s no mention of @convention(block) bridging to blocks. Can Swift implement C functions that have block arguments?

In Type Checking, we specify that Int bridges. In the C → Swift direction, size_t bridges to Int. However, in the compatibility header section, we say that Int is emitted as ptrdiff_t and UInt is emitted as size_t. Does this mean the C declaration void foo(size_t x) is imported as func foo(_ x: Int) but must be implemented as func foo(_ x: UInt)?

3 Likes

I really like this proposal, and I have no particular reservations beyond the ones mentioned above about its current state.

However, I’d like to suggest that we should encourage the SwiftPM team to consider taking this opportunity to improve the way SwiftPM exposes these features. Right now using the @implementation annotation can be done from SwiftPM, but to do it requires having a fairly deep understanding of how SwiftPM orchestrates builds and feels very janky and surprising. The result is that to get a good experience, it’s more effective to use Xcode or CMake.

When the only supported language for @implementation was Objective-C, this was defensible: the main value of this capability was migrating existing Objective-C codebases which are overwhelmingly Xcode projects. With the addition of C, this becomes much more interesting for a wide range of use-cases, including offering C programs access to Swift libraries on non-Apple platforms.

Similarly, having SwiftPM be capable of emitting the generated C/Objective-C header file for @c @objc annotations would also be extremely valuable for replacing entire libraries on non-Apple platforms where Xcode projects are not available.

Making the Swift Package model behave more coherently in this space is something I think the SwiftPM team should absolutely work on. I’m happy to make suggestions in more detail in this area if they would like assistance, or even co-author an evolution proposal, but I think now is the time to make this capability behave more sensibly in packages, as well as other build systems.

9 Likes

Just to make sure prior effort is cited, would a revisit of SE-0403: Package Manager Mixed Language Target Support be helpful here? It was returned for revision due to questions about C++ and plugin support. I'm not sure what the status of the original author is and whether they'd be able to pick it back up, though.

3 Likes

Yes, I think revisiting that makes a lot of sense! A few extra tweaks around products to get the interop headers emitted for certain product kinds regardless of dependency would be useful additions, but I think that proposal is a great jumping off point.

2 Likes

If we eventually support exposing structs from Swift to C as opaque data so they can contain arbitrary Swift types, would it also make sense to support exposing enums from Swift to C as opaque data as well, so that one can expose enums with associated values and/or with arbitrary Swift types? If so, how would that interact with the currently proposed behavior of @c enums?

The proposed direction seems quite useful. However, there is a related version that I wonder if it belongs in the same family. The proposed feature would allow for externalizing swift declarations into C, but what about the other way around? Particularly declaring in Swift external C functions, which would eliminate the need to wade through the modularization dance of headers or making tons of bespoke modules just to import a handful of functions. Right now there is an underscored version of @_extern(c, "functionName"), would this proposal potentially be expandable to a formalization of that feature too?

1 Like

A valuable addition would be supporting C compatible structs declared in Swift. We could consider exposing them to C as opaque data, or produce structs with a memory layout representable in C. Both have different use cases and advantages:

  • Using an opaque data representation would hide Swift details from C. Hiding these details allows the Swift struct to reference any Swift types and language features, without the concern of finding an equivalent C representation. This approach should be enough for the standard references to user data in C APIs.
  • Producing a Swift struct with a C memory layout would give the C code direct access to the data. This struct could be printed in the compatibility header as a normal C struct. This approach would need to be more restrictive on the Swift types and features used in the struct, starting with accepting only C representable types.

It could be a combination of the two approaches, e.g. if the type is C representable it'd be exposed to C as is, otherwise it could be exposed as an opaque value (a struct with some char's payload I guess).

@c struct S {
    var x: Int16
    var y: SwiftOnlyType
}

-->

typedef struct {
    int16_t x;
    char y[x bytes];
} S;

Similarly how we could mark a class @objc even though it might have individual fields unavailable in Obj-C.


I'm also curious what would happen with the names unrepresentable in C, like @c struct S { var 🐶 = true } or @c struct 🐶 {}

1 Like

I wonder if exposing types to C as opaque should be a completely different modifier, since we could in theory expose pretty much any Swift type that way, without otherwise affecting its layout and behavior. That way, @c is only for exposing implementations to C, with the necessary constraints and changes to behavior.

6 Likes

Swift Testing definitely needs this functionality! I'm in favour. Two questions:

Pointers to Swift types

A problem I run into from time to time with @_cdecl is that pointer arguments/results must be either pointers to some type declared in C or must be raw pointers. I understand the technical reason these pointers can't refer to Swift types, but would it be possible to relax this convention (read: lie to LLVM) and allow pointers to Swift types in signatures? For example:

@c func printIndirectly(_ string: UnsafePointer<String>) {
  print(string.pointee)
}

I could see this getting type-checked on the Swift side, then type-erased to UnsafeRawPointer in the emitted SIL and/or LLVM IR so that the compiler is none-the-wiser. So it'd be syntactic sugar for:

@c func printIndirectly(_ string: UnsafeRawPointer) {
  let string = string.assumingMemoryBound(to: String.self)
  print(string.pointee)
}

But with the benefit of more type safety when called from Swift.

Windows calling convention

Most platforms these days have a single common C calling convention, so @c is sufficient there. Windows, on the other hand, has multiple calling conventions that are in active use. What calling convention will be used on Windows in the absence of the "custom calling conventions" future direction? __cdecl? (You may want to ask @compnerd for advice here if you're not certain what the best choice would be.)

1 Like

(I stand corrected.)

1 Like

I think if we want to support non-default C calling conventions, we would make you declare the function in a C header annotated with the calling convention in question, and then @c @implementation could work.

(It's not totally implausible that it already works. @xymus?)

1 Like

Swift does support 32-bit platforms. The toolchain is hosted only on 64-bit platforms, if you are building for x86, you must compile on x64 and generate the 32-bit binary.

No, this is not niche, it is required for C++ interop.

4 Likes

Regarding the attribute name, has @stdc been considered? It doesn't have leaking implementation details like @cdecl and is not a single character.

1 Like

I think that I would prefer to avoid ā€œstdā€, because it potentially becomes confused with Microsoft’s ā€œstdcallā€ convention.

3 Likes

I'm OK with this being relegated to a "future direction", but I would like the proposal to have some discussion of how we want to handle new C features. The proposal as written treats C as a monolithic static target, and that isn't quite right. C moves very slowly, but it does move, and individual C implementations sometimes run far ahead of (or lag far behind) the standard.

Some examples:

  • before C99, we either wouldn't be able to export an API that used Bool, or we would have had to map it to some other type.
  • until quite recently, exporting an API that uses Float16 would require a compiler-specific extension, but we can use _Float16 starting with C23.
  • there's not a clear choice for binding Int128 in C; one can imagine using either C23's _BitInt(128) once its ABI problems have been resolved, or the GCC/Clang extension __int128_t.

In the first case, it seems fairly clear that we should just map Bool to _Bool. But MSVC didn't actually support _Bool until ... 2017 (IIRC). So if it were a decade ago, would we guard that API's visibility behind some sort of #if check to provide compatibility?

What about _Float16? There are definitely still people using C toolchains that do not have support for that type. Two options come to mind, but there are definitely more:

  • place any API that uses _Float16 behind an #if check in the generated header; doing that in backwards-compatible standard C23 is something like:
    #define __STDC_WANT_IEC_60559_TYPES_EXT__
    #include <float.h>
    #ifdef FLT16_MIN
    // API using _Float16 here
    #endif
    #undef __STDC_WANT_IEC_60559_TYPES_EXT__
    
  • only export such API if it also has @implementation and make it the header-writer's job to account for this (but: what C language settings and predefines are used by the clang importer to parse the header when we use @implementation?)
6 Likes

To clarify, what will the C code look like for each example in the Proposed solution? (I especially want to know about ā€œ@c enums".)

To bikeshed further, we have #if swift(>=6.3) and #if compiler(>=6.3). Perhaps we would need #if c(>=23)?