For a while now (possibly since Swift 1?) we've had an undocumented attribute @_cdecl
that exposes a Swift function to C. Several years ago (and while still at Apple), I alluded to the remaining decisions to we had to make to make @cdecl
a reality. I was hoping someone else would pick that up, but it hasn't happened yet; meanwhile, though, people are using it. So here it is: what we need to decide, along with my recommendations:
-
The name. I think
@cdecl
is our best choice, because@c
is so short, but either one is fine. People might also want to invent something generic like@convention(c)
on functions, so that we can scale to "expose this to Java" some day; I personally think this is overkill and will bog down the actual proposal. (If we do use some more generic syntax, make sure it still has a place to put a C name that's different than the Swift name.) -
Restrictions: Simply put, I think that
@cdecl
should only allow the subset of types allowed in C, excluding those allowed by ObjC. This makes it easier to write code on a Mac and not have to worry about your@cdecl
accidentally using an Objective-C type.If you do want a C function that takes or returns ObjC types, it should use
@objc @cdecl
—yes, both attributes, like@objc dynamic
or@objc optional
. This spends a bit of verbosity to make it obvious which attribute is doing the "expose to C" operation, which, again, is about not accidentally writing the wrong attribute while on a Mac and then using types you shouldn't.(I'm sure this is a bit controversial, so I'll talk through more of my thought process in a follow-up post.)
-
Closures: Exposed as blocks, not function pointers, as with
@objc
methods today. We already expose block-based APIs in the C side of Dispatch and SourceKit, and we can guard the declaration of the function in the generated header so it's only usable when blocks are available. -
Any / AnyObject: I think we should start off not supporting these, but in theory we can have an opaque SwiftObjectRef type where the only thing you can do is
swift_unknownRetain
andswift_unknownRelease
it (and maybe debug-print it as well). What I don't love here is that SwiftObjectRef ought to just beid
in ObjC modes, but with eventual C++ interop we'd have it presenting different types in C++ vs. ObjC++. We could resolve that by making it convertible toid
in ObjC++.Anyway, there might be answers here, but people have done just fine so far with just the usual C types, and the clunky-but-serviceable "bounce through Unmanaged" for passing a Swift object through
void *
, so I think this should be "Future Directions" rather than part of the Minimum Viable Product. -
Bool: Should be allowed, but do we follow
@objc
and make itBOOL
, or pick a more C-standardbool
? I'm inclined to go with the latter, possibly modified by whether the function is@cdecl
or@objc @cdecl
. (The same decision should be made aboutInt
being encoded asNSInteger
vs.intptr_t
.) -
Enum declarations:
@cdecl
should be allowed on enums, just like@objc
is today, with one adjustment: in non-ObjC, non-C++ contexts, we can't useenum Foo : uint8_t { … }
syntax for a fixed-size enum. Fortunately Apple has already figured out a fallback: if fixed-size enums are not available, definetypedef uint8_t Foo; enum { … }
instead—something the generated header can take care of automatically. It's not great (you lose code completion, for example), but it works at the ABI level. -
Struct declarations: I think
@cdecl
structs make sense but I don't want to roll them into this proposal; keep defining your structs in C for now. -
-emit-objc-header: The compiler currently uses "objc" in the names of the arguments to emit a header for a Swift module; that ought to be changed, but I'm not sure to what.
-emit-c-header
, even though it will sometimes have ObjC in it?-emit-header
feels a bit too generic.-emit-interoperation-header
is verbose without solving the generic. Suggestions? -
SwiftPM support: C-language targets ought to be able to depend on Swift targets, so SwiftPM ought to start generating this header, along with a module map for the Swift target. I don't know how hard this is and I'd appreciate help. I suspect it's not too bad because SwiftPM already supports C-language targets.
-
EDIT: Xcode support: Should be automatic for Xcode targets, because they're already set up to always generate ObjC headers! If there's additional Xcode/SwiftPM integration to do, though, someone from Apple will have to deal with that.
I have not implemented any of this in the compiler yet, but it's designed to be a fairly minimal set of changes from what's already there. I am willing to do that work if this goes to a proper proposal.