Type safety and incomplete C types

(NB: I'm a Swift beginner, so please bear with me).

Suppose I have a C library with incomplete types:

struct ctype_A;
struct ctype_B;
typedef struct ctype_B* ctype_B_handle;

and functions

struct ctype_B* create_B(void);

void consume_A(struct ctype_A*)

I'd like to expose such structs in Swift. One way is by holding OpaquePointer, and the other is (I guess?) holding the pointers directly. I'm not aware of other options. If I hold the pointers directly, I was quite surprised to see the following compile without warning or error:

var b : ctype_B_handle

b = create_B()
consume_A(b)

Note that ctype_A and ctype_B are not void *, though it seems like Swift is type erasing them.

Is there a way to hold pointers to C types such that the above would fail to compile? Even C++ is safer with this sort of thing.

Thanks!

There is not: all OpaquePointers are one type in Swift. This has been discussed in the past:

However, in my view this remains one of the most deeply unpleasant sharp edges of Swift.

I have two recommendations for how to deal with this. The first is to adopt the same strategy that I adopt with all wrappers around C: try to wrap these pointers at as low a level as possible in Swift types and don’t let them escape. Then, comment the code to say what the actual type of that pointer is and try to ensure you don’t get that wrong. This is brittle in the face of nasty little type changes in the C code, but those are usually rare.

The other option is to wrap these pointers in new, non-opaque C structs, and then redefine every function you want to call with a new thunk that takes your new structure and just extracts the pointer from it. This is profoundly silly, but it does return the type safety back in the Swift code, while allowing the C compiler to ensure you didn’t screw up. On the other hand, you now have to define these thunks for everything you want to do.

1 Like

Thanks for the quick reply, Cory!

One other thing you can do if you control the C code is to add the swift_wrapper attribute to your typedefs:

typedef struct ctype_B* ctype_B_handle __attribute__((swift_wrapper(struct)));
ctype_B_handle create_B(void);
void consume_B(ctype_B_handle);

This will make a RawRepresentable wrapper type whenever that typedef is used. The downside, though, is that you can't differentiate between const and non-const pointers to the struct this way in the C code. There's not really a great answer here (given the current state of things).

4 Likes

Is the swift_wrapper attribute documented somewhere?

Is there a list of all Swift-related __attribute__ s?

1 Like

Uh…sort of. If you look in the Swift branch of Clang at https://github.com/apple/llvm-project/tree/swift/master, you can find that the attributes are documented in the file clang/include/clang/Basic/AttrDocs.td, which is the form used to generate the list at Attributes in Clang — Clang 16.0.0git documentation. But since most of the Swift attributes aren't upstreamed to LLVM's Clang, they won't appear in the list on llvm.org, and I don't think Apple (or the Swift project) puts that list anywhere else. Even then, the documentation for swift_wrapper is woefully out of date (it's still using the original name swift_newtype, and the description is wrong).

I suggest filing a bug to make sure this gets documented somewhere on swift.org or developer.apple.com.

3 Likes