@_exported and fixing import visibility

Unless I misunderstand, what it gives us is the ability to use C without having to re-export everything. As @Joe_Groff mentioned upthread, sometimes there are reasons to use C right now where people might prefer to stay in Swift-only land. If they had the option to do that without the downside of having to re-export it might be very useful.

I understand that it's not a regression but it is something I think we should fix if we can (and it sounds like we can, the only question is whether we should given source compatibility, etc). The behavior of (2) and (3) is extremely undesirable IMO even if it is what we have today.

Isn't there a path forward that might break source compatibility but allows for automatic migration (at least via a temporary / deprecated @leaky import I mentioned earlier).

The compiler can't import "just the C part" of a Swift module because it doesn't know how the types match up with the Swift types. (The way this works in the importer is by basically throwing away the C declarations once they've been mapped back to Swift declarations.) I guess we could treat that as "loaded but not visible", though?

It's the kind of thing that's technically feasible but definitely non-trivial, and I haven't thought too much about the tricky bits. I was hoping we could define the problem away with the work on "implementation-only imports" and "re-exported imports", but that doesn't seem to be the case, and that means it's just a separate issue. Which means I won't be working on it right now.

Would this mean it's available to the library but not the client? I think this is pretty much the direction I'm suggesting, isn't it?

I see. We've lived with it this long so I suppose that's ok. :slight_smile: I'll be happy as long as it eventually gets some attention...

Yeah, "loaded but not visible" is my attempt to describe what the current Swift import does.

Okay, hm. I'll have to think about how to do this C thing.

FWIW, I would interpret "not visible" to include operators and extensions (which current Swift import does include)...

:-) I'm describing where we want to be rather than where we are, but since I did just say that probably won't get fixed as part of this work, maybe I should stop.

I’d like to note that this is not my expectation as an Objective-C developer. Clang modules have explicit export lists, and while export * is commonplace, it isn’t the only way to do things.

It is in fact exactly how the Clang modules dialect of C works. @import UIKit only exposes NSObject because UIKit’s module map says so.

On top of that (regardless of modules), C and Objective-C libraries can distinguish between public imports (ones that are in their public headers) and private ones (ones in private headers or implementation files only). They can also reduce the number of public imports required by using forward declarations. Swift doesn’t have either of these things.

It seems to me that C and Objective-C compatibility is being used to argue for a semantics that’s leakier than C or Objective-C require.

3 Likes

I don’t have the expertise that @jrose does in this area so I have been accepting his statement of the problem. If we can get away with more controlled semantics when dealing with C and Objective-C interop I would of course be very happy with that!

Ooh, I was hoping no one would bring up export *. Swift is currently pretty broken when people don't do export *, and I'm not sure Clang works well with it either. But on top of that, the module map for an Xcode-built framework containing Swift code currently always includes export * for the Swift part of the module, with no way to configure it. Part of the reason for this is because it's never a good thing when C code behaves differently with and without modules enabled, and while Swift generated headers currently don't support modules-off very well, they should be able to (SR-805).

We may not get forward declarations in Swift (for probably dozens of reasons), but this thread definitely covers adding the implementation-only imports. The tricky bits are around the things you want to expose as API, and then around the things you want to expose as ABI but not API. (The latter is where forward declarations are "usually good enough" in C.)

My bit to the discussion from user of the Swift and SPM.

I use SPM and I have plenty of targets. SPM requires you to explicitly specify the dependencies for each target inside Package.swift, but on the other side, Swift allows me to use transitive dependencies in my source code without updating Package.swift. Sometimes this breaks the build phase, because, as I think, the compiler meets some import-s of not yet built modules/targets. Then I have to dig into the problem, find the missing dependency and update my module inside Package.swift so build system would build the libraries in the right order. Usually this is a pain, because swift build will just print some not very helpful error about libc module. :slight_smile:

I agree that NSObject should be usable if you import AppKit or UIKit, but for non-Apple modules, I think, it will be beneficial to have the ability to disable the use of the transitive deps.

1 Like

I just ran into an issue related to this. Any new developments?

Not really. I've been more focused on module stability right now, but the general outcome of this discussion is that it's probably not worth doing anything clever for Swift vNext: just add "implementation-only import" and maybe "exported import" and leave the rest alone for now.

5 Likes

Thanks for the quick response. That much improvement would be enough for me.

Sorry to jump in like this but could you clarify whether swift vNext means swift 5 in this case?
I assume not, but I’d really like to have an official exported import and would just like a clear idea if I can expect this soon.

No, anything like this would have to go through the Swift Evolution Process, and it's well past the deadline for Swift 5 changes for that.

Thanks! I thought so, just had to check for sure since the thread’s been going for a year, I’ll keep a lookout on swift evolution

I developed a lib, it consists from two parts.
In first part I have a lot of custom infix operators.
Second part do @_exported import FirstPart.

Then my users do import SecondPart in their projects, by they facing with problem that infix operators from FirstPart are not available with just import SecondPart thought they should. So my users should also do import FirstPart.

Is there any chance if it will be fixed in Swift 5?

1 Like

@_exported is an implementation detail (as indicated by the leading underscore); its semantics are whatever it does, and can be changed at any point, so you should not depend on it doing anything specific.

Have you reported a bug? Bugs that are not reported are unlikely to get fixed in any Swift release. It's also possible that the behavior you're seeing is not a bug (it's hard to tell based on the limited description here), in which case reporting it gives you the opportunity to clarify exactly what you're doing and what behavior you expect to see, and for someone to help you figure out how to get it.

Swift 5 is really only taking fixes for critical regressions at this point, so realistically this is not going to change for that release.

I thought Discourse automatically linked threads that reference a particular thread, but I guess not. Check out "Update on implementation-only imports".

I'm late the party, but this thread was referenced in response to a question I had a couple days ago.

I want to build a Swift wrapper for SDL, an open-source graphics/event-handling library. At one extreme, it would make sense to write a comprehensive apinotes file for it and package it as a module (if only I could find comprehensive docs on how to do that). But that doesn't make it very Swifty, particularly in that you don't get ARC support.

So the next thing would be to wrap the API in a set of Swift classes and structs. I started to do this with SDL, and used the systemLibrary() in the SwiftPM to import the C API. I got stuck because there’s a set of #defines in the C API that I wanted to use early on to prove things out, but they weren’t visible to clients of my SwiftSDL module (I haven't yet tried @_export).

In the long run, this should be an OptionSet anyway, and I built that shim manually, but I feel like this is what I should be able to do (for example): Use apinotes to convert an enum to an OptionSet, and re-export just that OptionSet. Since I don’t control the C API, I need to be able to apply the apinotes as part of my library (SwiftSDL), which should allow the compiler to handle the fine-grained re-export.

Also, I don't like language of public import Foo. “Import” means one thing, “export” means the other, so I’d rather see import Foo; export Foo. But it’s a bit strange to export the imported library at the source file level. What happens when I have several source files? Any one of them could trigger the re-export. Seems like this should exist in a more central file for the module (e.g. Package dscriptor or apinotes or modulemap).

1 Like
Terms of Service

Privacy Policy

Cookie Policy