How to disable implicit Foundation imports?

As for Unicode tables, I've mentioned this before in other threads: after stdlib switched from ICU to its own tables, the situation became worse in terms of binary size for statically-linked apps. This is caused by Foundation not reusing the tables from stdlib and still relying on ICU. Thus statically-linked apps bundle two separate Unicode tables, one instance from stdlib, the other one from ICU.

7 Likes

I guess it is not yet capable, there's e.g.:

3 Likes

in your opinion, what are a few of the biggest obstacles preventing you from adopting pure-swift alternatives like swift-json, swift-png, or swift-url?

Ignoring those specific libraries, I really don't know. My guess is that Apple's leadership is unwilling to spend resources on such open source work and swift-corelibs-foundation was a political compromise within Apple that technically allowed Apple engineers to develop in the open because it was still controlled by Apple and could be used in the backend environments, even if it wasn't ideal for the language. This bootstrapped some open source functionality into Swift but also took energy away from native replacements, which is compounded by the lack of investment in Foundation from Apple more recently.

On the plus side it seems likely that the existence of swift-corelibs-foundation played some part in the recent open evolution proposals for URL and other Foundation improvements Apple is shipping this year.

But this is all speculation, which many really don't like here, so I'll leave it at that.

I don't think we should even be questioning OP's concerns about binary size - OP isn't using Foundation, so they shouldn't have to pay for it.

Whether OP's concerns apply to anybody else's projects, and how good/bad internet connections around the world may be, is not relevant to the original question.

7 Likes

this isn’t really helpful for those of us who make pure swift libraries because we are not part of Apple and not part of the corelibs project. so we can’t really make better libraries until we know what we specifically are doing wrong and not what Apple is doing wrong.

I've been trying to knock out the dependencies my Swift programs have on corelibs-foundation recently. The issue I've run into with using these foundation-less replacements is that a number of core dependencies already import Foundation. So then what's the point of depending on one of these libraries when I already have to "pay" for Foundation & everyone already knows Foundation? (the specific libraries I've come up against are Soto & async-http-client)

For me that's definitely the major issue. I do think that replacements for major currency types like Date and maybe Data (though some Collection<UInt8> is pretty good for that) would really benefit from inclusion in the standard library just because they become so much more useful when everyone can agree on the same type. Just like Result used to be.

Specific feedback on swift-json

This library looks really awesome and if I ever do manage to banish Foundation from my server components, it will probably be what I use for JSON decoding.

Encoding is obviously an important feature to make it useful for replacing Foundation and I see you have that on your roadmap, which is great.

The other thing I think would really help (just my 2 cents!) would be a very simple entry point into the package. It's a small thing, but just decoding a Decodable type (where I think most Swift programmers would want to start with a package like this) requires seeing the following types:

  • JSON
  • Grammar
  • JSON.Rule
  • JSON.Rule.Root

If I were the author of this package, I would add a convenience API for what I think is the most popular use:

extension JSON {
    static func decode<Data: Collection, Decoded: Decodable>(_ data: Data, as: Decoded.Type) throws -> Decoded { ... }
}

So then your getting started example could be:

import JSON
struct MyType: Codable { var x: Int }

let input = #"{ "x": 1 }"#
let object = try JSON.decode(input.utf8, as: MyType.self)

Then the section after that could get into more advanced things like annotating error messages and fancy grammar things. Basically I think when the community & tools have such a heavy bias towards Foundation you have to make it as simple as possible to use an alternative.

The WebURL getting started section is extremely focused by comparison, even though that library has tons of complex functionality:

import WebURL

var url = WebURL("https://github.com/karwa/swift-url")!
url.scheme   // "https"
url.hostname // "github.com"
url.path     // "/karwa/swift-url"

url.pathComponents.removeLast(2)
// "https://github.com/"

url.pathComponents += ["apple", "swift"]
// "https://github.com/apple/swift"
9 Likes

thank you! i’ve opened #47 to track this.

2 Likes

I would like to see a new checker tool that can list all the SDK-provided libraries. This is critical for cross-platform developers, e.g. they can check easily if it’s easy to support a new platform/

3 Likes

AHC having a Foundation dependency is something we'd like to address going forward. We should absolutely be able to use AHC without requiring Foundation, and the dependency was only added for a few specific API entry points that in principle we ought to be able to haul out.

Unfortunately, those API entry points are public API, so we'd need to mint a new major version to make this change. That's always a bit disruptive. With that said, AHC hasn't had a new major version for three years, so we probably have some breathing room to make that change. However, in a new major version we'll probably want to substantially revamp the older, Future-based API to be a bit less sprawling and a bit more considered. So there's some work to do there.

3 Likes

Perhaps worth mentioning - the WebURL port of async-http-client already supports building without any Foundation dependency.

By default, it builds with Foundation compatibility APIs, so you can continue to make requests using Foundation.URL and they will be converted to WebURL internally. The goal of those compatibility APIs is to demonstrate that projects such as AHC, Soto, and others, can adopt WebURL while retaining compatibility with clients using Foundation URL (of course, that requires a 1.0 release with a stable API).

The only API-breaking change is that HTTPClient.Request.url returns a WebURL - because I thought it would be sad if the WebURL port didn't return a WebURL :sweat_smile: - you can convert it to a Foundation URL if you like, and there's an .nsURL computed property which does it for you.

But if you have a package which uses the WebURL port of AHC, you can also build it using the following command:

NO_FOUNDATION_COMPAT=1 swift build -Xswiftc -DWEBURL_IDNA_USE_STDLIB_NFC

In that case, the compatibility APIs will be left out, and WebURL will use a standard library SPI for Unicode string normalisation (currently it has to get that from Foundation). I think it's accepted that the stdlib should expose this via a proper public API at some point; it is needed for basic operations such as String comparison anyway, so this is a temporary dependency.

One of the advantages of having a pure Swift implementation is that our Unicode processing (for Unicode domain names/IDNA) can use those same data tables and algorithms as the stdlib. String normalisation via the standard library is also 30-40% faster than doing it via Foundation.

As for binary size - I wrote a simple CLI program to make a request to a URL and stream the output to stdout. These are the sizes of the resulting binary, building in release mode with --static-swift-stdlib ("Regular" means using the official AHC repo):

Variant Size Difference
Regular (with Foundation) 77MB
WebURL 44MB -44%
Regular (with Foundation), stripped 48MB
WebURL, stripped 14MB -70%

And there are no missing features; it's everything AHC supports. It also includes IDNA everywhere (part of the URL standard) and it improves support for many URLs and redirects which Foundation does not process correctly.

Those binary sizes could probably go lower still; dead code (and metadata) elimination is a known issue in Swift, as has been mentioned above. Some of WebURL's interfaces might also be overly-generic or there may be other gains to be had; file a GitHub issue or send a PR if anything jumps out at you. It's also something I do keep an eye on and try to improve.

Anyway - I don't want to divert the thread. I just thought this was a relevant data point.

11 Likes

This PR https://github.com/apple/swift-package-manager/pull/6067 might also help to solve this type of issue as it offers a new type of resource which will generate code that isn't using Foundation, so the automatic import will not be present.

We could also consider to additionally offer an option on the .copy directive to opt out of any code generation entirely?

5 Likes

For anyone still struggling with implicit imports, I left a comment on a Github issue that should help. Although it's SwiftWasm-specific, the general swift-autolink-extract command should work for all users.

For SwiftWasm users, in particular, my comment outlines how to import the JSKit runtime bundle without Foundation.