It looks like it would possible.
The pitch could be named...
Inference of Sendable for existentials and existential containers
Motivation
The compiler is not able to detect that some existentials of non-Sendable
protocol that wrap Sendable
values can not create Sendable
violations:
// Program 1
protocol P { }
struct S: P, Sendable { }
func f(_ x: Sendable) { }
func demo() {
let s = S()
// ⚠️ Warning: type 'any P' does not conform to the 'Sendable' protocol
f(s as any P)
f([s as any P])
f(["hello": s as any P])
}
// Program 2
protocol Requirements {
static var value: any P
static var array: [any P]
static var dict: [String: any P]
}
struct Demo: Requirements {
// ⚠️ Static property is not concurrency-safe because it is not
// either conforming to 'Sendable' or isolated to a global actor;
// this is an error in Swift 6
static let value: any P = S()
static let array: [any P] = [S()]
static let dict: [String: any P] = ["hello": S()]
}
The compiler emits Sendable
violation warnings in both programs above. This is overly conservative.
The first program is safe, because:
- the
p
variable is constant, declared withlet
. - the value visible through the existential interface is Sendable: it is
s
.
The second program is safe, because:
- the globals are constant, declared with
let
. - the value stored in the global contains existential value(s) that wrap Sendable values.
There no way to use or reaffect those variables in a way that would create a Sendable
violation. They have all the qualities of Sendable
values.
The first program above does not quite look like a real program. But the second is quite similar to the example given in the original post of this thread. It demonstrates an undesired interaction of existentials with SE-0412 Strict concurrency for global variables.
Sketch of a solution
Inspired by SE-0414 Region based Isolation and SE-0418 Inferring Sendable for methods and key path literals, the compiler can assert the safety of the above program, by infering existentials to be any P & Sendable
.
This addresss the first program:
// No warning
f(s as any P /* & Sendable */)
f([s as any P /* & Sendable */])
f(["hello": s as any P /* & Sendable */])
In order to address the undesired interaction with SE-0412 and protocol requirements, we need something more.
Indeed, infering Sendable
as described above would miss the protocol requirement, and fail the developer intent which is to fulfill a customization point:
struct Demo: Requirements {
// Misses the `any P` requirement
static let value = S() // inferred as S
static let value = S() as any P // inferred as any P & Sendable
// Misses the `[any P]` requirement
static let array = [S()] // inferred as [S]
static let array = [S() as any P] // inferred as [any P & Sendable]
// Same miss for the dictionary
}
Declaring the type of static properties in order to target the protocol disables the Sendable
inference:
struct Demo: Requirements {
// No longer infered as any P & Sendable: warning due to SE-0412
static let value: any P = S()
// No longer infered as [any P & Sendable]: warning due to SE-0412
static let array: [any P] = [S()]
// Same miss for the dictionary
}
To address this, the compiler only uses the Sendable inference of existentials when considering the assignment to the constant global:
struct Demo: Requirements {
// value, of type `any P`, is assigned to a `any P & Sendable`: OK.
static let value: any P = S()
// array, of type `[any P]`, is assigned to a `[any P & Sendable]` which is
// sendable because arrays of sendables are sendable: OK.
static let array: [any P] = [S()]
// Same inference for the dictionary
}