Why are C argument labels not allowed?

Why is it an error to have argument labels for C function calls?

Arguably, this is not readable:

SDL_CreateRGBSurface(
    0,         
    8, 8,      
    32,        
    0, 0, 0, 0 
) 

It would be nice to optionally allow labels for imported C functions :slight_smile:

2 Likes

The answer is that they’re not allowed in C itself, Swift cannot allow for an argument label because there couldn’t have been one in the first place you’d call the function the same way in C.

I'm not sure I follow, it's hard to read in C so it has to be hard to read in Swift? For consistency?

I really doubt this implemented in a way where these are completely inseparable from the calling convention; if the labels have runtime impact... huh?

There's already a lot of convenience features like magic array to pointer conversions in Swift, why not have the choice to write:

SDL_CreateRGBSurface(
    flags: 0,         
    width: 8, height: 8,      
    depth: 32,        
    0, 0, 0, 0 
) 

There's no downsides, just a win for maintainability.
The LSP already autofills the argument labels, then just highlights everything that "no you can't do that actually, please delete these now".

You can import C functions with argument labels, but it requires manual work. By default,

void cfunc(int a, double b);

is imported as

public func cfunc(_ a: Int32, _ b: Double)

But with a swift_name attribute

__attribute__((swift_name("cfunc(a:b:)")))
void cfunc(int a, double b);

it is imported as

public func cfunc(a: Int32, b: Double)
4 Likes

For consistency with the C interop, yes.

Considering that you are calling C functions, written and declared in C, this is highly highly debatable, I think swift_name may personally work for your situation as Martin has pointed out, though I thought it was just for objective-c methods.

Well, okay, may I ask why? What's the benefit of enforcing no labels?

I am aware of swift_name, I'm not going to go into all the headers for SDL and add these obviously, I might as well wrap the entire library in a better API at that point.

Consistency, Swift is not the dynamic language where allowing just any label to be used for label-less C functions is acceptable, the answer is just that, consistency, freely allowing any labels to be used would create too much confusion when 2 different projects use the same library, in my opinion

1 Like

I agree, it might be confusing for some at first.

It could be argued though, that 8 unnamed integers are more so than the surprise of seeing an unexpected argument label.

In practice, 2 different projects will wrap this differently for convenience anyway, some will namespace it or abstract it away with structs, and you might find:

  • SDL.Surface(...)
  • SDL.createSurface()

and so on, complete with function overloading, classes, etc. Everyone has their own preferences and style, I've done that myself to a subset of SDL features too.

If you wanted everyone's project to look the same you would have to enforce one wrapping library, style, etc.

I think it would help to be a bit clearer about your expectations here, and where the labels would come from. C doesn't have argument labels in a way that maps 1-to-1 to Swift's argument labels, so there need to be some clear-cut rules about how this would work.

In C, these are all identical declarations for the same function:

int add(int x, int y);
int add(int a, int b);
int add(int, int);

In Swift, these are all entirely different functions which can coexist simultaneously:

func add(x: Int, y: Int) -> Int { /* ... */ }
func add(a: Int, b: Int) -> Int { /* ... */ }
func add(_: Int, _: Int) -> Int { /* ... */ }

Given a C function in a header, is your expectation that you will get 1-to-1 mappings like

int add(int x, int y) ↔︎ func add(x: Int, y: Int) -> Int
int add(int a, int b) ↔︎ func add(a: Int, b: Int) -> Int
int add(int, int) ↔︎ func add(_: Int, _: Int) -> Int

If so, then you actually couldn't call SDL_CreateRGBSurface the way you'd want; you'd need to write

SDL_CreateRGBSurface(
    flags: 0,         
    width: 8,
    height: 8,      
    depth: 32,        
    Rmask: 0,
    Gmask: 0,
    Bmask: 0,
    Amask: 0 
) 

because the full declaration of the function is

SDL_Surface* SDL_CreateRGBSurface
    (Uint32 flags, int width, int height, int depth,
     Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask);

If by "optionally allow labels", you mean that you would be allowed to label some arguments to the C function, but not others, I think this would be a pretty stark departure from how Swift currently works. (There are many follow-on questions like, "what makes C special that allows Swift to behave like this for C functions but not Swift functions?" and "what do we do about ambiguity when you omit an argument label?")


In the general case, Swift doesn't import C function argument names from headers because they're not reliable; because they're not required in C,

  1. Argument names are in too many cases an afterthought: there's no consequence for inconsistent or abbreviated names in C, missing argument names altogether, etc.; but, they would very much affect how they're called in Swift. Because of this,
  2. It's easy to innocuously change a name in a C header without thought (perhaps for the better as part of cleanup!); but, these changes which have absolutely no effect in C would be source-breaking and ABI-breaking changes when imported into Swift

The risk of easily breaking Swift code arguably outweighs the detriment of either consciously choosing names via swift_name, or writing a more Swift-focused wrapper for the underlying library.

13 Likes

Makes sense, thanks :slight_smile:

3 Likes