@_exported and fixing import visibility

Thanks for the reply @jrose.

General reply first: I forgot to mention that requiring public import for all visible things sneakily “fixes” (2)

Sure, at the cost of requiring us to transitively depend upon any modules whose symbols are used in the public interface of a module we depend upon.

and (3) if we get to a world where we don’t need to import the non-public things at all. Unfortunately, source compatibility, so it might end up being locked to a language version or compiler option or something.

Can you elaborate on this? I don't quite follow it.

There’s a very attractive solution here for modules distributed as source: expose custom options (via Package.swift, probably) that turn into conditional compilation guards for the library. (Or do something very clever with canImport.)

Are you talking about allowing library A to detect whether library B is available using a conditional compilation feature of some kind? This might be interesting, but it also sounds quite complex and isn't what I was thinking.

My concern is primarily that I want to be able to control whether my module directly depends on module B. If I choose not to depend upon B then clearly the APIs from module A that make use of symbols declared in module B will not be available to my module. Further, no symbols (including extensions and operators) declared in module B will be available to my module.

However, I don't mind if module B is available to module A internally - no conditional compilation required. This indirect dependency is a separate issue. I want to be aware of it but will usually not be as concerned about the indirect dependency. The flexibility I want to preserve is the ability to replace module A with some other module providing similar functionality. Specifically, I don't want any possibility that code depending directly on module B creeps into the codebase because importing module A implicitly results in importing module B. In fact, I would prefer if the project-wide linker configuration prevents import B in my code: I do not want the direct dependency.

To make this more concrete, let's imagine a networking library which makes liberal use of a Result module in its APIs. Now imagine this library adopts async / await (assuming that is added to Swift) as an alternative set of APIs. A lot of user code still depends upon the Result-based APIs so this library continues to support these APIs.

In this context, somebody is writing an app and wants to ensure async / await is used rather than callback-based APIs. They do not want to depend upon Result and do not want it to be available to their code. But they do want to use the async / await family of APIs provided by the networking library. This is a pretty pragmatic example of the kind of control over dependency management that I think is most important. Does this example help?

4 Likes