Bridging for swift-corelibs-foundation on Linux

Hello all,

This is a follow-up from an earlier discussion about bridging here.

After much consideration, we are going to go forward with implementing the "as" bridging for Swift on Linux. We have been exploring the concept of leaving this out since the beginning of the project. However, as we work towards finishing the remaining unimplemented features, we believe that the lack of parity in bridging is holding us back.

This missing feature causes some pretty mysterious behavior when porting code between platforms.

func f() -> Any {
    return NSNumber(value: 5)
}

let r = f() as? Double
print("It's \(r)")
// Darwin: It's Optional(5.0)
// Linux: It's nil

The primary areas where this causes confusion are also some of our most popular APIs: JSON serialization and property lists. These are APIs where we return an Any type and rely on the developer to cast to their desired result type.

JSONSerialization in particular has been an interesting case study. On Darwin, it creates NSNumber instances to represent the numeric values found in the source data. NSNumber may be cast to many Swift numeric types, via bridging. Due to the lack of bridging combined with the lack of an equivalent "AnyNumber" type in the standard library, JSONSerialization on Linux is implemented to return a specific number type (Double or Int) depending on what is detected in the JSON. This can be surprising if the rest of the code expects one type or the other.

Other API relies on reference semantics. The most common example is NSKeyedArchiver, which has explicit class API like decodeObject(ofClass:forKey:). Archiving also supports cyclical references, decoding mutable shared values, replacement of values, and more. While we believe that Codable is likely the future of Swift-friendly archiving, there is an immense amount of customer data stored in keyed archives. We believe it is important to continue to support reading and writing those files.

We considered several alternatives:

Remove all reference types from Foundation: This prevents the possibility of writing code which has a mysterious bridging failure, but it also disallows usage of important API like NSKeyedArchiver. Furthermore, if we add value type equivalents to more reference types in the future, should we remove those reference types too? If the answer is yes, we are setting people up to have API removed from underneath them. If the answer is no, then we will have an unprincipled mix of reference types that do and do not exist.

Furthermore, many Swift projects do use some Foundation reference types today. Yanking the reference types would make us instantly incompatible with those projects, should they choose to port to non-Darwin platforms.

Add an AnyNumber type to the standard library: This may be an effective solution to the NSNumber bridging problem above, but it requires adoption in both the implementations that produce numeric types today and the clients which receive them. We think this is an interesting future direction independent of the bridging discussion.

Add a Box type to the standard library: A standard Box type may be useful to replace some of the reference types in Foundation where the only requirement is reference semantics. We think this is also an interesting future direction, but also requires adoption across both implementations and client source code.

@millenomi has been working on implementing this and will have a PR ready soon.

9 Likes

Great! Will Core Foundation bridging also be supported?

1 Like

Thank you for working on Linux support of Foundation! :pray:t2:

1 Like

In general, Core Foundation on Linux is currently an implementation detail of Foundation rather than a public framework (for example, import Foundation will not give you CF symbols.) So, not in our initial pass, but if there's a compelling use case it may be useful to look into it. What did you have in mind?

CoreFoundation does have some useful functionality in general. Since it is currently used for Foundation, it already does work on Linux, it seems that making it available generally as a library could be useful. CFDictionary, CFSet, are helpful building blocks. It also means that the libraries would bridge into swift much better.

To be clear, CF is available and accessible on Linux if imported directly, and there are workarounds for the lack of as casts. My belief is that we can cover a large amount of use cases by starting with the NS* types, and then evaluate next steps.

1 Like