Why are opaque C pointers not imported as distinct types?

I just did a boo boo. I have a C header with

typedef struct Foo *FooRef;
typedef struct Bar *BarRef;

void doFooWithBar(FooRef, BarRef);

And in Swift accidentally called

doFooWithBar(myFoo, myFoo)

which gives no type error because apparently all opaque C types are just imported as typealiases for OpaquePointer, not as distinct types. Would it be at all possible to revise Swift so it imports opaque pointers as their own type (like, a subclass of OpaquePointer or so)?

I found one thread from 2016 that talks about this, with no resolution, really.

Being used to Swift's superior type-safety everywhere else, it is very easy to forget that it treats all imported C types as the same type, even when distinct even in an unsafe language like C. It's like having caltrops under-foot.

Is there a reason why it's done this way? In the days before Swift 3, I could imagine that it was done this way to allow calling CFRetain() on a CFStringRef, but given CFTypes all get imported looking and being memory-managed like Swift classes these days, I don't see why anyone would still need this type-unsafety.

I'd even settle for just the ability to mark up APIs where this behavior is (un)desired. (Ideally with separate type being the default, and an NS_OPAQUEPTR_TYPEALIAS qualifier or so for compatibility behavior.)


Possibly relevant: SE–384: Importing Forward Declared Objective-C Interfaces and Protocols

That proposal only deals with Obj-C interfaces/protocols; improving them isn't massively source breaking because any APIs that used them were dropped on the floor completely when imported, instead of degrading to some common type.

For C pointers, there's some fairly recent discussion here as well: Giant footguns, typealias extensions, and the C OpaquePointer


Some history and apologies:


Thanks, that's really useful. I'm pretty sure I followed this series when you posted these, not sure how I completely forgot about that.

Still, am I correct in assuming that this means that this behavior would theoretically be up for revision in a breaking release (like Swift 6, or a future Swift 7 or so?)


It’s hard to say. Swift versions apply on a per-library basis, so that you can use Swift 6 while still depending on a Swift 5 package. But what happens when the Swift 5 library vends an API that uses one of these OpaquePointer typedefs? The Swift 6 client had better see that as OpaquePointer, or it won’t find the write symbol name in the library binary. So we’d be talking about two Swift users of the same C API potentially treating the C API differently, within a single compilation.

I will note that if you control the C library in question, annotating the typedef as swift_newtype(struct) will give you a distinct type with OpaquePointer as its raw value. Not at all a complete answer, maybe not even a good one, but it’s there.

EDIT: swift_wrapper is the preferred way to spell it, not swift_newtype! My bad. (This is the attribute under NS_EXTENSIBLE_STRING_ENUM.)

1 Like

Oh, swift_newtype(struct) already is a thing and does what I need? That might be enough for my needs. I'd prefer sensible defaults, but overriding to get sensible behavior is fine.