Make a Type Itself -- Not Its Instances -- Conform to a Protocol

Protocol doesn't conform to itself?

EDIT: Eighteen more months of working w/ Swift, another major release (that provides a new diagnostic), and a comment from @AyBayBay makes me want to rewrite this answer. The new diagnostic is:

"Using 'P' as a concrete type conforming to protocol 'P' is not supported."

That actually makes this whole thing a lot clearer. This extension:

extension Array where Element : P {

doesn't apply when Element == P since P is not considered a concrete conformance of P. (The "put it in a box" solution below is still the most general solution.)


Old Answer:

It's yet another case of metatypes. Swift really wants you to get to a concrete type for most non-trivial things. [P] isn't a concrete type (you can't allocate a block of memory of known size for P). (I don't think that's actually true; you can absolutely create something of size P because it's done via indirection.) I don't think there's any evidence that this is a case of "shouldn't" work. This looks very much like one of their "doesn't work yet" cases. (Unfortunately it's almost impossible to get Apple to confirm the difference between those cases.) The fact that Array<P> can be a variable type (where Array cannot) indicates that they've already done some work in this direction, but Swift metatypes have lots of sharp edges and unimplemented cases. I don't think you're going to get a better "why" answer than that. "Because the compiler doesn't allow it." (Unsatisfying, I know. My whole Swift life…)

The solution is almost always to put things in a box. We build a type-eraser.

protocol P { }
struct S: P { }

struct AnyPArray {
var array: [P]
init(_ array:[P]) { self.array = array }
}

extension AnyPArray {
func test<T>() -> [T] {
return []
}
}

let arr = AnyPArray([S()])
let result: [S] = arr.test()

When Swift allows you to do this directly (which I do expect eventually), it will likely just be by creating this box for you automatically. Recursive enums had exactly this history. You had to box them and it was incredibly annoying and restricting, and then finally the compiler added indirect to do the same thing more automatically.

How to solve Protocol 'Result' as a type cannot conform to the protocol itself error in swift 5.6

The function processResult shouldn't be generic, a generic will require a concrete type that conforms to Result at compile time.

func processResult(_ result: Result)

Problem with associtedtype when referring to a protocol metatype

This is the standard "protocols do not conform to protocols" question. Contract is a protocol that requires conforming types to also conform to Initializable. It is not itself Initializable. In fact, this is a canonical example of why protocols can't conform to themselves in the most general case. If it did, I could write Contract.init() and get back a...what? There's no way to allocate memory for "an abstract Contract."

Since Contract does not conform to Initializable, it can't be Option directly. Swift today also lacks any mechanism to talk about protocol inheritance. You cannot say that Option must be a protocol that requires Initializable. It's just beyond the Swift type system.

That said, this is a pretty inflexible approach. It requires that every type accept a trivial init, which is very limiting to what kinds of types can conform. There are much better ways IMO to implement this kind of factory.

Get rid of Initializable, get rid of the static requirement (you'll see why later), and replace the type with a builder function.

protocol OptionListFactory {
associatedtype Option
var availableOptions: [() -> Option] { get }
}

That causes the extension to be tweaked this way:

extension OptionListFactory {
// Remove `static`
func build(code: Int) -> Option? {
if code >= 0 && code < availableOptions.count {
return availableOptions[code]() // Call the builder function
}
else {
return nil
}
}
}

And a simple factory:

protocol Contract {}

struct Contract01 : Contract {}
struct Contract02 : Contract {}

struct ContractFactory : OptionListFactory {
let availableOptions: [() -> Contract] = [
Contract01.init, // init rather than self
Contract02.init,
]
}

But what if you have some type you want to vend that needs some extra information to construct? Doing it this way makes that straightforward.

// Elements need a name
protocol Element {
var name: String { get }
}

struct Element01 : Element { let name: String }
struct Element02 : Element { let name: String }
struct Element20 : Element { let name: String }

struct ElementFactory : OptionListFactory {
let availableOptions: [() -> Element]

// And now we can assign that name
init(prefix: String) {
availableOptions = [
{ Element01(name: prefix + "01") },
{ Element02(name: prefix + "02") },
{ Element20(name: prefix + "20") },
]
}
}

let ef = ElementFactory(prefix: "local")
let e20 = ef.build(code: 2)
// e20?.name == "local20"

By making the factories instances rather than using static, they are much more flexible and can be configured. And by using functions, you can compose in ways you may not have considered. For example, you could add a debug print statement every time an option is created:

struct DebugFactory<Base: OptionListFactory>: OptionListFactory {
let availableOptions: [() -> Base.Option]

init(base: Base) {
availableOptions = base.availableOptions.map { f in
return {
print("Creating object")
return f()
}
}
}
}

// Works exactly the same as `ef`, just prints.
let debugFactory = DebugFactory(base: ef)
debugFactory.build(code: 2)

How to make a swift class conform to a protocol that requires a protocol-conform type for one of its properties?

You can do this with associated types.

Declare an associated type in ProtocolA:

protocol ProtocolA {
associatedtype BType: ProtocolB
var b: BType? { get } // Note the change in the type of b
}

This is saying that b is an optional of some type that conforms to ProtocolB. What type exactly? That depends on the conformers of ProtocolA.

In A's extension, you specify what BType is:

extension A: ProtocolA {
typealias BType = B
}

And you're done!

As a result of this, you won't be able to use ProtocolA as the type of a variable:

var protocolA: ProtocolA? // error

Because you don't know what b's type is.

Can't create an Array of types conforming to a Protocol in Swift

Let's say, if we could put an instance of Thing into array foos, what will happen?

protocol Foo {
associatedtype BazType
func bar(x:BazType) -> BazType
}

class Thing: Foo {
func bar(x: Int) -> Int {
return x.successor()
}
}

class AnotherThing: Foo {
func bar(x: String) -> String {
return x
}
}

var foos: [Foo] = [Thing()]

Because AnotherThing conforms to Foo too, so we can put it into foos also.

foos.append(AnotherThing())

Now we grab a foo from foos randomly.

let foo = foos[Int(arc4random_uniform(UInt32(foos.count - 1)))]

and I'm going to call method bar, can you tell me that I should send a string or an integer to bar?

foo.bar("foo") or foo.bar(1)

Swift can't.

So it can only be used as a generic constraint.

What scenario requires a protocol like this?

Example:

class MyClass<T: Foo> {
let fooThing: T?

init(fooThing: T? = nil) {
self.fooThing = fooThing
}

func myMethod() {
let thing = fooThing as? Thing // ok
thing?.bar(1) // fine

let anotherThing = fooThing as? AnotherThing // no problem
anotherThing?.bar("foo") // you can do it

// but you can't downcast it to types which doesn't conform to Foo
let string = fooThing as? String // this is an error
}
}

Swift 5: how to specify a generic type conforming to protocol when declaring a variable

An associated type is used when you want your protocol to work with a variety of types, think a Container protocol that might have several methods all dealing with one contained type.

But your protocol is not that, it doesn't need to know any other types to specify the necessary behavior, so get rid of the associated type.

protocol Pipe {
func await() -> Void
func yield( to: Any, with listener: Selector ) -> Void
}

class Foo {
var imageSource: Pipe & Renderable
}


Related Topics



Leave a reply



Submit