Update on implementation-only imports

Probably also worth mentioning that implementation-only import won't affect symbol mangling and you still can't have two modules named SecretKit that end up in the same "package graph".

1 Like

Sure, right. It's a modifier on the import only; it doesn't affect the existence or contents of the module being imported.

How a public type can override a method without confirming to a SecretKit protocol or being able to inherit a SecretKit type ?

[edit] I just read the PR link description. This is about overriding methods declared in extension of existing type inside SecretKit [/edit]

1 Like

I don’t understand the why. What is the purpose of this feature?

See the thread @_exported and fixing import visibility for more information.

If you have a dependency that is entirely an implementation detail, such as how you do JSON parsing - I could see this being useful.

It also gives compiler support for enforcing the intended use of the dependency.

2 Likes

Yep, @Mordil's got it. There are times where a dependency should not be exposed to clients, whether that's because of Swift's current leaky imports that we hope to fix at some point (@_exported and fixing import visibility), or because you're making something for binary distribution and won't be distributing your dependencies. That latter is Apple's main use case (public frameworks being implemented in terms of private frameworks), but it's also a step in addressing one of the biggest limitations in the Xcode 11 binary framework support: you can't use packages with it, because those become dependencies for your clients as well.

(I compressed a lot into that one sentence; check out the WWDC session I co-presented. The problem here is talked about at 37:16, albeit briefly.)

Now, this alone doesn't solve all the problems there, because you still can't have two copies of the same package in the same address space. But it's a step, and it would at least allow for local packages, prefixed with your organization's name in some way, to be used in XCFrameworks. As long as you're careful about not packaging them into two separate binary frameworks, anyway.

In general we really do want to improve the cross-module dependency story in Swift, both for source packages and for binary packages. We as library authors should be able to separate compilation dependencies from link dependencies and control how both are passed on to a library's clients.

(sorry for not responding sooner)

11 Likes

Here is something that has a difference between -enable-library-evolution and not.

If you have:
something.h:

struct A { ... };

something.swift:

struct B {
  var v: UnsafeMutablePointer<A>;
};

With -enable-library-evolution it works, but without it complains with: "error: cannot use struct 'A' ... has been imported as implementation-only"

1 Like

I just wrote a post on my experiences splitting a package into modules (Swift packages and module dependencies ) where one of the issues I raised was automatic "re-export" of conformances and extensions added to imported public types. My suggestion was

but hopefully this feature will deal with this issue!

1 Like

Any news on this? We really need this feature :(

1 Like

@zienag Jordan Rose is not at Apple anymore maybe someone else at Apple will continue his work maybe not, we shall see.

1 Like

The core work is already present in the compiler; it's just (unfortunately) all using internal, non-stable language features.

4 Likes

One thing we noticed when using this feature is that you're required to match imports across the entire module with or without @_implementationOnly. We are thinking that this could lead to a cumbersome case where a module A with 50 files (and therefore up to 50 imports) depends on B as implementation only, but then A wants to expose B in its public interface, and the developer has to remove the annotation from all @_implementationOnly import Bs. Is there any interest in adding a compiler level flag that treats all "normal" import B declarations as @_implementationOnly import B instead, so therefore doing this transition would be a matter of removing that flag? Ignore the spelling but something like -implementation-only-import B

4 Likes

To add to Keith's comment, I was also wondering about the possibility of being able to use per-file @_implementationOnly imports. In other words mixed use of regular imports and @_implementationOnly imports within a module. If this was allowed, people could see exactly which files were exposing (or leaking) dependencies. Seeing this info could help for maintenance and module structuring.

EDIT: It's a warning right now to used mixed imports, which means it's allowed if you don't use -warnings-as-errors, but we do use that. A case where control over warnings could be nice.

I've submitted a PR for discussion that allows the example that @Dave_Lee is suggesting Remove warning for conflicting implementationOnly imports by keith · Pull Request #29230 · apple/swift · GitHub

This was a deliberate decision, actually. I had originally implemented it the way you described, but we immediately found that it made it too easy to accidentally publish the dependency on B without the compiler telling you anything. In the long term, perhaps "new dependencies" should be part of the purview of the API checker (@Xi_Ge, who should that idea go to?) and then the compiler can switch / go back to allowing mixed use of implementation-only.

2 Likes

Thanks for the context! Do you have any thoughts on something like this:

As a general principle, I don't like module-wide command-line flags that affect the interpretation of source code, because it means you can't look at the source code in isolation and know what it's doing. But I haven't really taken stock of what the current set of flags are these days (-enable-library-evolution is doing exactly that, for example), or thought about whether this is a case where it makes sense.

I do know that the core team was interested in changing the default. I personally think that's going to be intractable—everyone using types from Foundation in their public APIs is going to have to learn about this new kind of import. So maybe an "increased paranoia level" flag makes sense here.

(I also know package manager folks have long been interested in making dependencies more explicit, so it might be good to get them into the discussion.)

5 Likes

Hey @jrose thanks for pushing this feature forward!

In my company we're jumping all in on Packages and this is a key feature for us, and we are a bit anxious atm since this is still not officially supported.

Since up in the thread someone said he is not at apple anymore, would anyone else have any news on this front?

1 Like

We have an (XC)framework now, lets call it A.xcframework, that uses some other packages built as (XC)frameworks, eg B.xcframework and C.xcframework, that we would like to make private dependencies of A.xcframework. The reason is, packages B and C are widely used dependencies (for example packages like Moya, Realm etc), which the final app might want to use as well, and it might want to use versions of B and C that are different from those that A is built with. Is there a way of achieving what we need, and does @_implementationOnly import solves our problem (I assume it doesn't, reading the comments above)?

1 Like

We are having the same issue (the main app uses custom-built SQLite and the package uses another version that comes with the dependency AppSync Amplify, resulting in many errors: Redefinition of 'sqlite3_...' ).
Did you find a solution?