How to disable implicit Foundation imports?

I'm not sure that's doable in practice. Let's say we did have some kind of analysis that determines whether we skip generating the resource accessor even if there are resources in a target. This would mean any code in the library that uses the accessor would not compile anymore. Would we exclude any source files referencing Bundle.module from the build as well and hope that would still end up building something usable?

It seems to me that this is ultimately a task for the library author, they have to provide library products in a way where the resource use becomes optional.

There has to be some way to package up this functionality such that any library which bundles resources doesn't end up doing something roughly equivalent to @_exported import Foundation.

Does the generated resource accessor live in the library? Can its use of Foundation be @_implementationOnly?

Yah, that should definitely be doable.

That said, does that end up helping? If a client links the library, it would still transitively bring in Foundation, wouldn't it?

This is exactly what we're doing with JavaScriptKit. Neither the library nor its users need to reference these resources (a few runtime files written in JavaScript) from Swift code, but external tools (potentially SwiftPM plugins in the future) discover and copy the resources to correct paths during development and deployment to make them visible in the browser.

1 Like

I guess yes. Not if we find way to optimize it away.

But at least that should solve the problem of symbol visibility. It’s worth as a quick fix.

Ah interesting, I wonder whether we can do an import scan or something and if there are no imports of Foundation, we would skip generating the accessor.

1 Like

My concern is specifically the piece where Foundation NSString extensions are being made available implicitly. Here, the code size impact is from users inadvertently using those extensions, which wouldn’t be possible if the only import of Foundation is an @_implementationOnly import in a dependency. Currently, SwiftPM is injecting a non-@_implementationOnly use of Foundation which is causing the downstream trouble, if I understand correctly.

4 Likes

I think this is the import in question and we should be able to make it implementation-only.

We will probably have to make any such change depending on the tools-version of the package being >5.7 in order to not break existing packages that might accidentally rely on Foundation being available implicitly.

2 Likes

Arguably, it’s a straight-up bug in user code to depend on a leaking implementation detail of SwiftPM for an implicit import of Foundation, which most users would appreciate having the opportunity to fix with explicit import Foundation. Well, not merely arguably—this entire thread is that argument actually being made. Debugging the status quo is much less intuitive than an error message telling you to import Foundation.

7 Likes

I wonder if an additional measure that may be viable, which wouldn't requiring import scanning, etc., is to vend this extension of Foundation as an overlay, i.e., only when both Foundation and the library are imported.

6 Likes

@NeoNacho thanks for tracking down the code in question.

Candidly, and at the sore risk of pushing the discussion in an unproductive direction (not at all my intention), but the reliance on Foundation for essentially very simple things like this (literally just a path string) makes my blood boil just a little bit. I should qualify that statement by saying my aversion is mostly because Foundation is a huge monolithic entity that only has partial (and varied!) support for 3rd party platforms, and has inherited a lot of Objective C strangeness, recreating APIs for many basic types in fundamentally non-Swifty ways, and implementing other, ostensibly useful functionalities in entirely non-type-safe ways. For all intents and purposes that I'm familiar with, if devs are able to to avoid Foundation – at least in non-Darwin code – they should.

For that reason, it really bugs me that Foundation takes so much room in Swift's collective consciousness, and I feel the community would be so much better off with more modular and "Swifty" alternatives. I gather the decision makers at Apple feel the same, at least to some degree, which is why there is a push for the numerous and ever-increasing number of open source SwiftPM packages that have been published over the last years.

What I'm trying to say is, in my humble opinion, the move away from Foundation as a legacy monolith can't happen quickly enough, and I would greatly appreciate essential tools like Swift Package Manager taking a stance to avoid essentially forcing its adoption like this.


Edit: As an attempt to make this post more productive and less "complainy": what if we had a standalone module for Bundle, that could be imported here instead of "the entire thing"? That module could also be @_export imported by Foundation for reasons of backwards compatibility

10 Likes

I greatly appreciate that you've moved so quickly to improve this situation @xwu, thank you!

2 Likes

this is not at all an unproductive direction, in fact i personally think it is a very productive thing to talk about (at least as a library author) and the only reason it is not talked about more is because someone still has to write the smaller, more modular frameworks to replace Foundation, and we do not always have time to take a 3 week detour to write a pure swift library so more often than not we just go down the Foundation route because it is the path of least resistance.

the issues with Foundation are well known and if anything i think the things you have mentioned are only touching on the tip of the iceberg.

again i do not think anyone disagrees with what you are saying but it is a very big task and “i hate Foundation” is necessary but not sufficient. in particular many of us pure swift library authors are not good at cocoapods or XCode so writing libraries that don’t import Foundation but only compile on amazon linux and only compile on this week’s nightly toolchain is not a step forward it is really just more of the same if we are being honest.

if we want to make progress it is important i think to focus less on “we need more foundationless libraries” because there actually are a lot more than people think but they don’t get adoption because going from linux to macOS/iOS/watchOS/tvOS is just as hard as the other way around and the ecosystem is still fragmented just in a different way.

I would appreciate a mod moving what is essentially a new thread here (starting with my post above) actually into a new thread. I think it could be quite productive to discuss potential solutions.

For me, I'd be happy if I could just import and link, say, the Foundation implementation of JSONDecoder without the kitchen sink coming along for the ride. In the JavaScript world the solution to that is known as tree shaking but, in both languages, this is easier said than done with automated tooling alone. As evidenced in this thread, that doesn't seem to work at all for Wasm, and I also haven't had any luck doing so on Android – i.e. it doesn't work where we need it the most.

I personally would be happy with a solution that modularises small parts of Foundation and reduces interdependencies between those modules; maybe in the future these could be moved out of the library entirely. The difficulty I see is that Foundation seems to be deliberately written in such a way that increases interdependencies between its parts – philosophically it appears to want to be "the platform", and as a codebase appears to implicitly reward dogfooding from within.

In the immediate-term my main concern is about that philosophy itself – Foundation being "the platform" probably makes sense on Darwin because the library is shipped with the OS and has years (decades?) of battle testing, so embracing it there is essentially low cost and all win. The issue is when that mentality leaks, for example via SwiftPM, into platforms (arguably all non-Darwin platforms?) where the opposite is true: the cost of linking Foundation is high, and the implementation itself is incomplete and often incompletely tested (-> buggy).

4 Likes

"At the beginning people complained that Foundation doesn't exist on non Apple platforms, now when it is finally there people doing their best to avoid it..." :sweat_smile:

Considering static linking only: It looks like you would be happy if strip did its job better - and it doesn't (*) - perhaps then this is the strip whom to blame. This is not Foundation specific - the same could be said about any large library that explodes the app binary size even if you use a single function in the library. Splitting things into micro packages to avoid app size issue works but feels like a roundabout / workaround solution.

(*) or maybe strip does work and without it the app size would be much larger? :thinking:

PS. I understand that you'd prefer 3MB to 26MB app, it does fell wrong and I am fully with you on that. However is this just that, i.e. "it feels wrong" or do you have some real practical issue with that, like you are shipping your app to your customers over 3G connection and it takes ages? To be honest I don't even know how many MBs is the app we are shipping, won't be surprised (or care much) if it is 5MB or 50MB.

7 times out of 10 i am installing apps when i am cellular data or spotty wifi or spotty wifi that displaces cellular data because of iOS’s fallback and if it takes too long i give up on the download and my friends are the same. and if it is a website and not an app store app i am even more likely to bounce.

so i totally understand why this is an existential problem for app developers.

2 Likes

Nobody ever said this. We wanted Swift native versions of some of the functionality in Foundation, and for it to be open source. Apple's eventual answer was swift-corelibs-foundation and... well... here we are.

4 Likes

Yes, I would say the vast majority of internet users don't have an Ethernet cable running to their device (especially if it's a phone or a tablet). You don't have to use 3G to experience this, just try downloading something at a hotel, a coffee shop, an airport, or at some residential area with heavy WiFi congestion. And I'm only speaking of 1st world countries that have a privilege of having something better than 3G available.

1 Like

I'm talking about a web app, which has much more ethereal "download" characteristics (i.e. it's not downloaded and then "done"), and is updated more often than a typical mobile app. A typical mobile app may well be ~50MB, but a typical web app tends to be much smaller than that.

Also, users don't make as much of a conscious choice to download a web app – there is no popup asking them if they want to download it –, so it doesn't feel good at an ethical level to regularly spam them with 10s of MBs of downloads that may or may not be cached.

Also, we (essentially have no choice but to) mirror our app in China, which comes tied with endless amounts of performance considerations. The connection between China and the rest of the world is tenuous at best, and people's internet connections within China are also not always as good as they are elsewhere.

I could continue, but in short, it matters a lot :slight_smile:

2 Likes

Interestingly to see people commenting upon the easy "why 3MB is better than 26MB" point yet nobody picked up upon why strip doesn't seem to be doing what it's supposed to do to keep the app size still around "3MB". Is it the case that "String.trimmingCharacters" indirectly touches the whole/most of the Foundation (perhaps unicode tables (1))? Or is strip inherently incapable / unreliable?

(1) and if it is indeed due to inclusion of unicode tables perhaps Foundation could have some alternative that won't explode the app size? bike shedding:

"string".nonUnicodeAwareTrimmingCharacters(in: .nonUnicodeAwareWhitespaces)
Somewhat relevant tangent.

All good reasons above why we need to keep app binaries small. Yet we (the industry as a whole and Apple in particular) have got ourselves in a situation when we increased all binaries by some 30-70% (my estimate, please correct if you have a better figure) by switching all apps to 64 bits, even if 99.99% (another estimate of mine) of apps don't utilise the benefits of being 64 bits. Isn't that a mistake?

3 Likes