3

I would like to define a struct with automatic conformance to Encodable, where the struct has an array of Encodable elements:

struct EncodableBagB: Encodable {
  var values: [Encodable] // ❌ Type 'EncodableBagB' does not conform to protocol 'Encodable'
}

If we take a step back and not use an array, the way to solve this is to move Encodable to be a generic:

struct EncodableBagA1<T: Encodable>: Encodable {
  var value: T //  Okay, but doesn't work for arrays
}

If I do the same with an array, I lose the ability to store heterogenous elements:

struct EncodableBagB1<T: Encodable>: Encodable {
  var values: [T]
}
EncodableBagB1(values: ["A", "B"])
EncodableBagB1(values: ["A", "B", 1]) // ❌ Type of expression is ambiguous without more context

The only workaround seems to be to introduce a ton of duplicate generic structures:

struct EncodableBagB1_2<T0: Encodable, T1: Encodable, T2: Encodable, T3: Encodable>: Encodable {
  var value0: T0
  var value1: T1
  var value2: T2
  var value3: T3

  init(_ encodable0: T0, _ encodable1: T1, _ encodable2: T2, _ encodable3: T3) {
    value0 = encodable0
    value1 = encodable1
    value2 = encodable2
    value3 = encodable3
  }
  //  Okay, but very verbose and requires a bunch of duplicate structures (e.g. T0, T0-1, T0-2, T0-3, etc.) for each permutation
}

Can Swift 5.7's some or any keywords help in this situation, or are there any upcoming proposals which would eventually address this problem? What is this problem called (so I know what to look for in the future)?

I'd prefer to avoid manual type erasure (e.g. AnyEncodable) if possible, since I'd like to use Mirror on the items within the array.

Note: I'm using Encodable to simplify the example. The actual protocol I'm eventually using looks more like this: protocol Foo: Encodable { func bar() }.


Here is an exhaustive list of everything I tried:


// MARK: Single value

struct EncodableBagA: Encodable {
  var value: Encodable // ❌ Type 'EncodableBagA' does not conform to protocol 'Encodable'
}

struct EncodableBagA1<T: Encodable>: Encodable {
  var value: T //  Okay, but doesn't work for arrays
}

struct EncodableBagA2: Encodable { // ❌ Type 'EncodableBagA2' does not conform to protocol 'Encodable'
  var value: any Encodable
}

struct EncodableBagA3: Encodable {
  var value: some Encodable // ❌ Property declares an opaque return type, but has no initializer expression from which to infer an underlying type
}

// MARK: Array

struct EncodableBagB: Encodable { // ❌ Type 'EncodableBagB' does not conform to protocol 'Encodable'
  var values: [any Encodable]
}

struct EncodableBagB1<T: Encodable>: Encodable {
  var values: [T]
}
EncodableBagB1(values: ["A", "B"])
EncodableBagB1(values: ["A", "B", 1]) // ❌ Type of expression is ambiguous without more context

struct EncodableBagB2<T: Encodable>: Encodable {
  var values: [any T] // ❌ 'any' has no effect on type parameter 'T'
}

struct EncodableBagB3<T: Encodable>: Encodable { // ❌ Type 'EncodableBagB3' does not conform to protocol 'Encodable'
  var values: [some T] // ❌ An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class
}

struct EncodableBagB1_1<T0: Encodable, T1: Encodable, T2: Encodable, T3: Encodable>: Encodable {
  var value0: T0
  var value1: T1
  var value2: T2
  var value3: T3

  init(_ encodables: Encodable...) {
    value0 = encodables[0] // ❌ Cannot assign value of type 'any Encodable' to type 'T0'
    value1 = encodables[1] // ❌ Cannot assign value of type 'any Encodable' to type 'T1'
    value2 = encodables[2] // ❌ Cannot assign value of type 'any Encodable' to type 'T2'
    value3 = encodables[3] // ❌ Cannot assign value of type 'any Encodable' to type 'T3'
  }
}

struct EncodableBagB1_2<T0: Encodable, T1: Encodable, T2: Encodable, T3: Encodable>: Encodable {
  var value0: T0
  var value1: T1
  var value2: T2
  var value3: T3

  init(_ encodable0: T0, _ encodable1: T1, _ encodable2: T2, _ encodable3: T3) {
    value0 = encodable0
    value1 = encodable1
    value2 = encodable2
    value3 = encodable3
  }
  //  Okay, but very verbose and requires a bunch of duplicate structures (e.g. T0, T0-1, T0-2, T0-3, etc.) for each permutation
}

Senseful
  • 86,719
  • 67
  • 308
  • 465
  • You cannot have *automatic* conformance for this case. The Swift compiler will not generate an automatic conformance. It's not a fundamental problem of the language; the compiler just doesn't have this feature currently. I believe this question is a close duplicate to https://stackoverflow.com/questions/73123640/is-it-possible-to-make-an-any-protocol-conform-to-codable/73124904#73124904 and the answer there applies. Let me know if you believe there is more to this question, and I can reopen this. – Rob Napier Aug 06 '22 at 14:46
  • That explains it, thanks. Since there is no way to do what I wanted, I ended up using the `AnyEncodable` type eraser strategy, but made sure it has a `wrapped: Encodable` property so that `Mirror` finds all the properties on the original type too. – Senseful Aug 06 '22 at 20:43
  • Consider implementing CustomReflectable in that case. You can return the wrapped value's mirror as the mirror transparently. – Rob Napier Aug 06 '22 at 21:58

0 Answers0