swiftswift-protocolsvariadicfunction-parameter

"some" keyword with variadic parameter in Swift


protocol myProtocol {}

func doSomething(with params: (some myProtocol)...) {
    // Implementation goes here
}

extension Int: myProtocol {}

doSomething(with: 1, 2, 3)

Compilation error at the func declaration line:

'some' types are only permitted in properties, subscripts, and functions

I could simply omit the keyword altogether, but then it is considered to be 'any' by default. Consequently, I cannot pass the params to a function that expects [some SyntaxProtocol] as a parameter.


Solution

  • Why you can't use some in variadic parameters

    Disallowing usage of opaque types(some Protocol) in variadic parameters was a conscious decision by the language developers. Because it would conflict with another proposed language feature called variadic generics:

    Variadic generics

    An opaque type cannot be used in a variadic parameter:

    func acceptLots(_: some P...)
    

    This restriction is in place because the semantics implied by this proposal might not be the appropriate semantics if Swift gains variadic generics. Specifically, the semantics implied by this proposal itself (without variadic generics) would be equivalent to:

    func acceptLots<_T: P>(_: _T...)
    

    where acceptLots requires that all of the arguments have the same type:

    acceptLots(1, 1, 2, 3, 5, 8)          // okay
    acceptLots("Hello", "Swift", "World") // okay
    acceptLots("Swift", 6)                // error: argument for `some P` could be either String or Int
    

    With variadic generics, one might instead make the implicit generic parameter a generic parameter pack, as follows:

    func acceptLots<_Ts: P...>(_: _Ts...)
    

    In this case, acceptLots accepts any number of arguments, all of which might have different types:

    acceptLots(1, 1, 2, 3, 5, 8)          // okay, Ts contains six Int types
    acceptLots("Hello", "Swift", "World") // okay, Ts contains three String types
    acceptLots(Swift, 6)                  // okay, Ts contains String and Int
    

    Source: https://github.com/apple/swift-evolution/blob/main/proposals/0341-opaque-parameters.md#variadic-generics

    So, it's not some bug, or oversight. It's working as intended.


    What is the meaning of that some keyword in parameter type declaration anyways?

    But what should you do with your code? Well, we gotta check why that language feature was added in the first place, and what it is doing "under the hood":

    And it was introduced to make function with heave usage of generics "lighter" and easier to read.

    To turn code like:

    func horizontal<V1: View, V2: View>(_ v1: V1, _ v2: V2) -> some View {
      HStack {
        v1
        v2
      }
    }
    

    into this:

    func horizontal(_ v1: some View, _ v2: some View) -> some View {
      HStack {
        v1
        v2
      }
    }
    

    Both of those snippets are equivalent, version with some View during compilation just turns into generic function version.

    As with opaque result types, some P indicates a type that is unnamed and is only known by its constraint: it conforms to the protocol P. When an opaque type occurs within a parameter type, it is replaced by an (unnamed) generic parameter. For example, the given function:

    func f(_ p: some P) { }
    

    is equivalent to a generic function described as follows, with a synthesized (unnamable) type parameter _T:

    func f<_T: P>(_ p: _T)
    

    Source: https://github.com/apple/swift-evolution/blob/main/proposals/0341-opaque-parameters.md#proposed-solution

    What should you do

    As it was said before: this synctactic sugar is explicitly unavailable for variadic parameters. So, only obvious solution would be to fall back to the "unsugared" syntax and use generic parameters:

    func doSomething<P: myProtocol>(with params: P...) {
    // Implementation goes here
    }
    

    And it will work as intended (if you intended for that all parameters in variadic list should have exactly the same type, that confirms to myProtocol, ofc).