FoundationNetworking

All,

there was a pitch to move URLSession and some related types to a new module some time ago. We have been working on implementing it, and a patch is now available in the swift-corelibs-foundation repository.

I want to spend a moment addressing the original feedback on the proposal. There are a number of places in clang and the Swift compiler where the name of the Foundation module is hardcoded. That is unfortunate; it made the consequences of moving relevant Foundation types into a "base" module widespread. We did some exploratory work to address these issues, but found that this greatly complicated some very performance-sensitive sections of the infrastructure that special-case Foundation, such as ObjC bridging.

In the end, we choose the simpler approach of just providing a separate module that will require an additional import line — the patch adds diagnostics, so that people using affected types with 5.1 will receive a clear message at compile time detailing what to do.

Wait, what does 'an additional import line' mean?

If you use one of the affected types, you can make the source file compatible with both Darwin and Linux by adding:

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

at the top. You don't have to guess which files, though; when compiling using Swift 5.1, the compiler will notice when you need the new line and tell you with a message like:

/YourApp/SourceFile.swift:510:13: error: 'URLSession' has been renamed to 'FoundationNetworking.URLSession': This type has moved to the FoundationNetworking module. Import that module to use it.

I wanted to hear from you — is this going to affect your project, and to what degree?

9 Likes

I am strongly against this change “as is”:

  1. It creates more inconsistencies and source code incompatibilities between Apple platforms and other platforms which is clearly against the objective of swift-corelibs-foundation. import Foundation should keep importing all symbols of Foundation.
  2. It creates new very user hostile API. If I understand correctly, with this change, this basic snippet would crash on all non-Darwin platforms, because Foundation will avoid linking FoundationNetwork:
import Foundation

let url = URL(string: "https://forums.swift.org/t/foundationnetworking/24769")!
print(Data(contentsOf: url))

Apple would never design an API like this for its own platforms thus it should neither do that for other platform.

If the objective is to avoid linking curl at the moment of importing Foundation maybe FoundationNetwork could be lazily loaded when required.

1 Like

Thanks, I very much agree and like on having this as a necessary step in order to get better library composability.
Having to specifically import the FoundationNetworking it’s not much of an issue, actually it feels it may even benefit app architectures (since we must import the networking stack, we can easily notice which component is currently using it).

That won't fly if you want static binaries. And the usual libcurl's that ship with Linux systems don't come with all of their dependencies as static libraries. So right now linking libcurl/libxm2 means no static binaries.

@millenomi If this is finally implemented, could at least a dummy module be added to Darwin so that...

import Foundation
import FoundationNetworking

...can be cross-platform code?

10 Likes

I would take it one step further:

import Foundation
import FoundationNetworking

should be the new "correct" way to do it on all platforms. If you use the symbols without the correct import, the compiler should carry on as-if you did and show a warning, so old code still compiles but new code is cross-compatible by default.

It's not pretty but it's better than the alternative. Perhaps we could add some kind of tag on the @available attribute to make it a little bit less awful...

5 Likes

I’ve been torn about whether to fatalError or not in the contentsOf case.

On one hand, it is almost never the case that a developer allows arbitrary URLs in those calls — if a developer passes a URL to Foundation that Foundation alone cannot possibly open, it is certainly a programmer error. (It bears noting that this is a fatal error with a clear message that the programmer can act upon to fix the issue.)

On the other hand, I could fail at runtime by throwing an error, meaning that a developer issue would fail gracefully instead of tearing down the process.

My yardstick, however, has been “can a programmer honestly just presentError(_:) whatever is thrown and not feel like the whole thing has failed the user?” and given that this requires developer action to correct, it doesn’t quite pass that bar.

1 Like

(Also I want to note that I hear your Darwin comments; I’m just not at liberty to comment about what happens, or could, to the closed-source side of the platform.)

3 Likes

I applaud the effort for Foundation to have submodules, but this change would currently break a lot of code we have on iOS, Linux and Android.

  1. For starters, we use URL in situations that are completely unrelated to Networking, like loading assets from the file systems. URL and URLComponents are also used in our model layer (Codable types) and having to import and link against libcurl in places we don't even use it goes against what I believe is the goals you are aiming for, specifically composability and modularity. FoundationNetworking should include libcurl usage like NSURLSession, like the name implies, for actual networking classes. Would URL be an unresolved identifier if I have it in my model layer, or when using FileManager? or would it simply crash at runtime? Both scenarios are not an improvement for Foundation.
  2. I agree it would be an improvement to use just specific parts of Foundation, but import Foundation should work as it has always been on all platforms, in other words it should be an umbrella framework, like Cocoa on macOS. This change will break pretty much dozens of libraries in our codebase, and making this a change only on Linux fragments and worsens out development process for Swift on Linux.
  3. If FoundationNetworking is going to exist, it would be inconsistent to (as mentioned before) to make it Linux-only change, but as well as it only applying to Networking. Should Foundation be composable, I think this should go through the Swift Evolution process, be the same on all platforms, and most importantly, affect more than just Networking. If we cannot find a good way to apply the same concept of FoundationNetworking for FileManager, UUID, Date, etc through the evolution process, then the idea should be scrapped entirely.

I believe this is the list of types that are moving. (Lily, correct me if I'm wrong.) URL and URLComponents are not on the list; they will still be in plain Foundation.

Correct. URL and URLComponents remain in Foundation.

To respond to the other points, I refer to my initial post re: the consequences of moving things to FoundationBase (which would be required to have the Foundation module import everything); and my comment on Darwin above.

Now that we are "suffering" this change. Why not doing this?:

If the code calls String(contentsOf:) with a remote URL and FoundationNetworking is not loaded, Foundation should dlopen FoundationNetworking instead of crashing the executable. That way we would avoid any unexpected crashes while avoiding to link curl until it is needed.

Also, not having a FoundationNetworking module on Darwin is really unfortunate. Instead of getting more cross-platform, it seem that we are going in the other direction.

2 Likes

Understood. The issue here is that this change, as noted, is for dependency management — clients that need this need the library to never be loaded even if present, not even accidentally.

I hear you re: cross-platform, but as it involves things such as Darwin timing, I cannot really elaborate on what happened there, sorry. It is my intent to figure out a better solution to this.

Well, that is as simple as never loading remote URLs through Data(contentsOf:) and String(contentsOf:). Currently if those clients do that they will simply crash.

IMO the lazy-loading behavior seems the cleanest and more understandable solution for the vast majority of clients (which do not bother about linking curl or other dependencies). The more cautious clients should be the ones which must be careful about what API are using and which to avoid (currently the burden is on everyone else who have to know how to avoid some API without any compiler warnings, because a call to it can crash the process for not importing other module which is not required for using the API and worked totally fine until now and keeps working fine on Darwin as is!?…)

:+1:

Well, that is as simple as never loading remote URLs through Data(contentsOf:) and String(contentsOf:) . Currently if those clients do that they will simply crash.

Not all code in an executable is under the control of the client itself if it's using dependencies. In that case, we want to surface the issue to the client's author explicitly rather than allowing third-party code to load the library.

(It is perhaps a good place here to note very loudly that using network URLs with String(contentsOf:) and Data(contentsOf:), and similar API, is very strongly discouraged in pretty much all scenarios, and this code really should transition to an appropriate URLSession setup instead.)

Why? I think they are very convenient. In fact, they seem the only way to do synchronous URL requests on Foundation which is very useful for scripting.

In any scenario where Foundation is commonly deployed, which for swift-corelibs-foundation includes server code, blocking the current thread with no way to control that blocking is typically a liability.