Int/Int32 differences between Linux and macOS

I have a Swift Vapor app that runs on macOS (for development) and in a Docker container on Debian on a 64-bit RPi 4 (for deployment). I notice when I build the Docker image, using swift:5.10, I get a mismatch in a call to setsockopt: it expects Int32, but the IPPROTO_TCP and TCP_NODELAY end up defined as Int:

74.01 /build/.build/checkouts/SwiftMODBUS/Sources/SwiftMODBUS/MODBUSContext.swift:200:36: error: cannot convert value of type 'Int' to expected argument type 'Int32'
74.01                         let result = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &flag, socklen_t(MemoryLayout.size(ofValue: flag)))
74.01                                                         ^
74.01                                                         Int32(     )

The function and the constants are Int32 on macOS. I can cast them as the diagnostic fix-it suggests, and it compiles without issue on macOS, but I wondered if something about this wasn't slightly broken on Linux.

This happens all the time because you're relying on defines/constants/enums imported from C. IPPROTO_TCP is probably defined either in a C enum or via #define IPPROTO_TCP 6. Neither really defines what type this is and Swift then defaults to Int. That however doesn't match the type the receiving function receives.

On Darwin, this is less common because the SDK's headers there pay more attention towards adding type 'hints' for Swift (and even for warnings in C).


If you actually want a recommendation: For socket code: Use SwiftNIO and you won't have any of these issues. NIO defines proper Swift helpers such as .tcpNoDelay whilst still giving you full access to any other socket options you may need. Plus, it'll be asynchronous, you can get access to a whole host of useful tools writing network software and with NIOAsyncChannel you even get to write it all in Swift Concurrency code with working cancellation etc. You can even re-use your wire protocol implementation and have it run over Network.framework via NIOTransportServices if you ever want to run this code on mac/i/watchOS and use the platform networking library.

3 Likes

I’m not sure I like that the import behavior is different in Darwin like that, but the workaround is painless enough. It just would've been nice to see the error when building for macOS.

Unfortunately I'm using libmodbus, and so I (think) I'm stuck with setsockopt. I am working on a pure Swift implementation of it, using NIO, but for now this’ll have to do.

Glibc/musl/etc is a very different SDK than what we ship with macOS. It's to be expected there are differences in these SDKs which have the side effect of different import behavior in Swift.

1 Like

My additional recommendation is to lean into CInt. In C, setsockopt is defined in terms of int and thus the correct type to convert IPPROTO_TCP to is not Int32 but CInt. It doesn’t matter in this case, but it’s not hard to imagine situations where it would.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

If the exact type in Swift is ambiguous or differs between platforms, using .init(IPPROTO_TCP) is also a good solution because the compiler will infer the type for .init to be "whatever setsockopt() expects here". The initializer is then a no-op at the assembly level.

6 Likes