Making C API "Swift friendly"?

Hello folks,

First, a bit of background. I'm writing yet another Swift wrapper for Raylib, mainly for use in my hobby projects. To be honest, I'm not convinced there's a real need for it, as there are already solid wrappers, such as this one and this one. Moreover, since Swift interacts well with most C code—and where it doesn't, there are effective workarounds—Swift wrappers for Raylib aren't strictly necessary. However, this has been a great learning exercise, and I want to see it through to completion.

Since C and Swift interop is already strong, I believe that a good Swift wrapper for a C library should provide more Swift-friendly abstractions around C types and functions when necessary. Otherwise, it's just writing C with Swift syntax.

So far, I've encountered several issues, and I'd like to know what the "Swift-idiomatic" way of handling them is. A rationale behind any suggestions would be greatly appreciated.

Inlining

Most function bodies are very short callbacks to C functions, and when called, they often run inside the game loop. Do I need to annotate them with @inlinable for the compiler to inline them when consuming my library? For internal use, is there any reason not to annotate them with @inline(__always)? After all, inlining a C function call instead of calling a function that merely wraps the same C function doesn't increase code size but reduces overhead.

Naming

Raylib's naming strategy is fairly clear, especially when looking at the comments. However, since C lacks function overloading, function names tend to be longer than necessary. Also, because I'm doing this for learning purposes, I want to provide more idiomatic Swift alternatives where it makes sense.
For example, what would be a good way to translate something like:

bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2)

into Swift while adhering to Swift's guidelines for fluent usage and argument labels? Should I:

  1. Keep it mostly as-is (assuming I've renamed all CheckCollision functions to collide)?
public static func collideCircles(center1: Vector2, radius1: Float, center2: Vector2, radius2: Float) -> Bool
  1. Group the parameters into tuples, something like this?
public static func collide(circle1: (center: Vector2, radius: Float), circle2: (center: Vector2, radius: Float)) -> Bool

This is a good and clear way to logically group arguments and make my intention clear, and it aims to be fluent usage, but it tuples feel weird to me.
3. If I define my own Circle struct (Raylib doesn’t have one and it could be convenient), should I instead just use this function?

public static func collide(_ circle1: Circle, and circle2: Circle) -> Bool

And in that case, should I avoid exposing the original C function in my API, making this the only standalone function to check collisions between circles? (Of course, Circle and other Shapes should still have their own methods to check collisions with other shapes, including circles.)

  1. I'm open to better suggestions than the ones I've listed here.

Handling Resource Ownership

How should I handle types that own resources? Should they be made ~Copyable with proper deinit, or should I wrap them in a class (also with deinit)? Or should I simply leave them as they are and handle resources manually (which seems error-prone)?

I’d really appreciate your suggestions and comments.

Providing a more 'swifty' API when wrapping C functions is something I always like.

In this case, for this to be effective, it would be nice to provide a document/list where you map the C names to their Swift counterpart. This is because of the way the Raylib documentation relies very much on a list of functions and a description of what they do.

For instance, when I need to find something in Raylib, I first turn to the cheatsheets on the Raylib site and those contain all the functions by their C name. So once I know which C named function I need, I need a simple way of finding the corresponding Swift name.

Are you also looking into wrapping the Raylib functions with a 'begin' and an 'end' into closure based ones? I.e.:

Raylib.beginDrawing()
        Raylib.clearBackground(.rayWhite)
        Raylib.drawText("Hello, world!", 425, 25, 25, .darkGray)
Raylib.endDrawing()

would become:

Raylib.draw {
    Raylib.clearBackground(.rayWhite)
    Raylib.drawText("Hello, world!", 425, 25, 25, .darkGray)
}

Good luck with your project!

2 Likes

Replacing begin/end with closures has already been implemented.
The cheatsheet translation is planned; however, it will have to wait until I finish porting most of the functionality and stop modifying the API every few hours.

1 Like