I'd like to discuss Swift’s @_cdecl attribute by removing the underscore—renaming it to @cdecl—to clearly signal that it is a stable, first-class feature for exposing Swift functions to C. I believe this change could significantly enhance Swift’s role as a cross-platform systems language. Here’s the context and key points for discussion:
Learning from Rust’s Success:
Rust has made great strides in making itself easily exposable to other languages by using mechanisms like extern "C" combined with tools such as cbindgen to generate C headers. An excellent example of this approach is the bp7-rs library. Although written in Rust, bp7-rs is designed to be used seamlessly through a CFFI, meaning that downstream callers can integrate with it without concerning themselves with its Rust origins. I love the idea that Swift could follow a similarly thoughtful design philosophy, making our libraries just as accessible.
Swift’s Cross-Platform Potential:
Swift already has the capability to be a cross-platform systems language. With a well-defined public attribute for C interop, Swift can produce both static and dynamic libraries that integrate seamlessly with other languages via a CFFI.
Aesthetic and Semantic Clarity:
Currently, the @_cdecl syntax includes an underscore, which in Swift’s naming conventions often signals internal or private functionality. Removing the underscore to become @cdecl would make it clear to developers that this is a supported, public API meant for production use.
Stability of the Feature:
Given that @_cdecl has remained unchanged despite its extensive use, now is a good time to formalize it by removing the underscore. This small change would communicate that Swift supports interoperability in a first-class manner, encouraging developers to adopt it with confidence. There can certainly be more creative solutions in the future as discussed in this thread Formalizing @cdecl. But I just think that we should try to make some strides to support our stance as an excellent cross-platform language.
Supporting Documentation:
Alongside this change, I propose we create a dedicated blog post on the Swift website. This post would explain how to build binaries usable by other languages through a CFFI, offering practical guidance for developers looking to build cross-platform libraries in Swift. While we already have some discussion around C/C++ interoperability (for example, Swift’s C++ interop documentation), there’s a gap when it comes to illustrating how Swift can generate binaries for use from other languages. This proposal—and the accompanying blog post—could fill that gap and further clarify Swift’s vision for inter-language operability.
Removing the underscore with a well-crafted blog post would showcase Swift’s potential as a language for creating robust, cross-platform libraries. This could significantly enhance Swift’s reputation in the systems programming arena.
Thanks for kicking off the thread @mbalex99! It was great to catch up during FOSDEM The topic indeed has been lingering around for a long time, and it may be time to give it a shot for real.
I was talking with @Douglas_Gregor last week to see what's missing so we could formalize cdecl, and it does not seem to be that much missing.
The main things seem to be:
ban objc types from @cdecl annotated methods
but allow them with @cdecl + @objc
introduce @cdecl support for int backed enums
some cleanup in generated headers -- like the __OBJC__ attribute and how blocks are handled (if objc)
There's some other problems Doug had more context on, but it does seem like something viable to consider in the near future...
I'm personally not a fan of this name. We already have @_extern and @_expose controlling how symbols are imported and exported, I personally don't find either of these names self-explanatory or discoverable. Even the existing @convention(c) feels more appropriate, I'm not sure if there's a reason for that not to be used instead?
In summary, I would prefer a holistic approach to naming that cleans it up and addresses these issues first, instead of just removing the underscore.
Yes, naming we could totally formalize it as @expose(c, ...).
What it would do would be equivalent to today's _cdecl -- so maybe thread should be just suggesting "formalize _cdecl" (that's exactly the 2020's thread name hah)
can we improve cdecl so that it type checks method parameters too? e.g. you have an externed c method def and its implemented by Swift -- there is no type checking that the parameters match.
@jrose had a good writeup in a previous thread covering most of the to-do items for formalizing _cdecl, most of which still looks relevant.
Swift declarations don't have any inherent convention of their own; the compiler generates an entry point with the right calling convention as needed by the ABI, by dispatch tables, or by uses of the function. Regular Swift functions can be used as @convention(c) functions (assuming their parameters and results are C-compatible, and they don't capture any context), and conversely functions marked @_cdecl can still be used as regular Swift functions, including as closures. That's one reason @convention(c) isn't reused, since @_cdecl only adds the C calling convention entry point to the ABI of the module but doesn't fundamentally affect how the declaration can be used otherwise. (And, yeah, @escaping is irrelevant to @convention(c) since C function pointers never carry any context.)
I'm very much in favor of formalizing @_cdecl, given how widely it's already used. I personally don't mind the name staying as it is (@cdecl), but if there's any push to unify naming across attributes that's great.
I would also like to see improved typed-pointer support if this feature becomes officially supported.
I recognize that Swift types aren't generally visible in C, but I should be able to write UnsafePointer<T> in a C function signature even if T is an opaque type. It could be represented C in as const struct T *, for example.
This is important for type safety in the function's implementation, but also for correctness on arm64e where the pointee type may affect pointer signing.
We had some experience with @_cdecl and there were a few issues we hit:
@_cdecl cannot control the visibility of the symbol. If the function is made public, it appears in the swift interface and causes conflicts when it is also present in a C header in the same module.
The return value is always autoreleased. E.g. @_cdecl func MyObjectCopyDescription(_ obj: MyObjectPointer?) -> CFString does not return a retained CFString.
Compiler does not help with nil checking and it's an undefined behavior when NULL is passed to a non-Optional argument. We ended up making everything Optional and force unwrapping them to deterministically crash.
I think it's better to follow the pattern of @objc @implementation where the function needs to match the declaration in a C header. It would be easier than manually matching the arguments and coming up with new annotations in Swift.
I think that most of the concerns that I had were not possible to address with @_cdecl and would need to be changed to provide the necessary control for it to be used in now less theoretical ways, i.e. C++ interop. I think that a few other names were proposed on the cites thread that Jordan started which had additional parameters and could support the necessary usecases.