Update on implementation-only imports

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?

Unfortunately, not

Are they linker or compiler redefinition errors?
If compiler ones, then @_implementationOnly should help, because it will make package's version of SQLite hidden from the compiler and it won't need to load this transitive dependency during compilation of the main target.

If a linker one, having the package with all of it dependencies in a separate dylib could help, to hide duplicative symbols from the linker.

1 Like

Compiler defined. Yes, seems like @_implementationOnly solves the problem, at least partially :+1:t3:

FWIW Android have implementation dependencies which stop leaking them to the consumers

We are implementing third party XCFrameworks for our clients. We are planning to use @_implementationOnly import in our SDK due to our framework pluggability requirement. We are skeptical about using this in our SDK due to the following warning.

Could you please confirm that if we use this @_implementationOnly import in our framework, do we get any problems when our Clients are submitting their apps to AppStore ?

Underscored Attributes Reference :
WARNING: This information is provided primarily for compiler and standard library developers. Usage of these attributes outside of the Swift monorepo is STRONGLY DISCOURAGED.

You'll be fine in terms of review. There aren't any guidelines that prevent the usage of private Swift flags.

1 Like
//this causes EXC_BAD_ACCESS in consuming module
@_implementationOnly import AMQPClient

public struct Subscription: Sendable {
    var channel: AMQPChannel //class from @_implementationOnly import
}

tl;dr: use with caution ; )

Thought I'd share the horrors I just went through because I tried using this in a package, maybe this helps somebody else:

I wanted a package that wraps our inter-services RPC/messaging in a nice, high-level package using rabbitmq-nio internally. I stumbled over the @_implementationOnly attribute and thought "nice, this prevents me from 'leaking' implementation details - if I want to swap out stuff later I can without having to adjust all our services". compiler warnings seemed reasonable, all seemed fine.

when running tests I started to get BAD_ACCESS errors in the weirdest places - very, VERY tough to debug for me. since I was using a lot of new concurrency stuff, NIO under the hood, and a few self-made unchecked sendable implementations I initially thought I must have screwed up data access somehow.

I kept getting this call stack in some objc_retain thing, which I figured must have to do something with ARC but for me it was miles away from a straight-forward error.

so, long story short, my package passed out a public struct with an internal field containing a class from the "_implementationOnly" module. and when the struct went out of scope, the ARC stuff seemed to get very confused. no compiler warnings or errors, just a pretty nasty crash at runtime.

Oddly, the crash had nothing to do with deinit or any actual call, and happened with unowned just as much as with weak. Just having this Subscription type it in a function's signature caused mayhem.

As a confusion bonus: if you import the implementationOnly module in the calling app/test as well, everything works just fine.

3 Likes

FWIW this is a "known" bug I filed ~2 years ago: rdar://78129903 (Runtime crash when using struct from another module with @_implementationOnly member)

I'm not sure if it ever got a corresponding public Jira or GitHub issue.

1 Like

Yeah, there are generally reasons why features are experimental. One of the missing features that would really be necessary in order to "productize" @_implementationOnly is a diagnostic which would tell you when you're using it incorrectly.

3 Likes