Implicit Sendable conformance for a "package"

We are in the process of making sure our code is fully Sendable compliant and one thing I'm wondering is if there is a way of having "public/package" types be implicit Sendable without having to write the conformance all the time.

The issue may be related to the "package access control" thread but that one is huge and I'm not sure if it relates to this at all so hence I ask in a separate thread.

The code I'm talking about follows the modern "modularized" app popularized by PointFree. Basically the app target is just a shell for different libraries defined in SPM. In my case I have a single Package with multiple libraries in it. This means that the types in the libraries are public so they can be used on other libraries in the same package, which according to the docs disables implicit conformance:

Many structs and enums satisfy the requirements of Sendable , and having to explicitly write out ": Sendable " for every such type can feel like boilerplate. For non-public structs and enums that are also not @usableFromInline , and for frozen public structs and enums, the Sendable conformance is implicitly provided

Given this structure, and what the Sendable proposal says, is there a way to tell the libraries that their types should be implicitly Sendable? I'm just trying to avoid the boilerplate of annotating each type. I'm not too experienced with library evolution but maybe using frozen helps? Although that would mean I have to annotate every type anyway right?

I know it may be a weird ask, and if I need to annotate all types is not a big deal, at this point I'm just curious. Any help is welcomed, thanks :)

After reading more docs about this I'm still not sure why my public structs are not implicitly Sendable.

The proposal says:

and for frozen public structs and enums, the Sendable conformance is implicitly provided

Then reading the frozen documentation:

When the compiler isn’t in library evolution mode, all structures and enumerations are implicitly frozen, and this attribute is ignored.

And from Swift docs:

Library evolution support is turned off by default. Frameworks that are always built and distributed together, such as Swift Package Manager packages or binary frameworks that are internal to your app, should not be built with library evolution support.

And checking in Xcode I don't have BUILD_LIBRARY_FOR_DISTRIBUTION enabled either.

So given all of this I deduce that: My library has library evolution off -> so public structs are frozen implicitly -> so Sendable conformance should be implicit.

Is this chain of deductions wrong?

Or the other issue I can think of is that I still don't fully understand how SPM plays into this? Maybe although Xcode doesn't have it enabled the fact that I use a local SPM package to make libraries than then are imported into Xcode some how makes it a different resilience domain and thus disabling the implicit frozen?

Here is an example of the code I'm trying:

I would expect PublicModel to not have a warning since there is no library evolution enabled (in theory).

This code example is compiling a library module (library scheme in Xcode) from SPM.
I'm using Xcode 14.2 and the target in SPM has concurrency checks enabled:

SwiftSetting.unsafeFlags([
            "-Xfrontend", "-strict-concurrency=complete",
            "-Xfrontend", "-warn-concurrency",
            "-Xfrontend", "-enable-actor-data-race-checks",
])

Just a guess but maybe the compiler ignores implicit frozen structs because otherwise your structs will have Sendable conformance automatically applied or not depending on a compiler switch which seems like a recipe for disaster.

I'm leaning towards "incomplete wording in the proposal", in line with what @jeremyp said: The part about frozen public types being implicitly Sendable only applies to @frozen types in library evolution mode, and not implicitly-frozen types.

1 Like

Yeah, if I remember the history correctly the rationale behind suppressing the implicit Sendable conformance for public types was, in part, “you wouldn’t be able to later add a non-Sendable property (or otherwise make the type non-Sendable) without breaking clients,” but the set of invalid changes in this regard are already invalid for resilient types that are @frozen, so the rationale doesn’t apply. I agree the wording here could be improved, since as we’ve currently defined the behavior it seems there’s a contradiction.

Thanks for the replies! Seeing there is no way around it I’m happy annotating the types, no biggie.
Is just very confusing to me that some rules apply to public types in my package and others don’t. I would expect that with library evolution turned off (which is the default) I would be able to get implicit rules as long as it’s been compiled together. I understand that if i ship a precomplied library that’s bad but when is not the case this just adds burden.
I wish there was a magic “i know it says public, but it’s all used in the same app” flag that gave implicit sendable conformance ^^