Christopher
Christopher

Reputation: 1455

Protocol extensions on Structs causes compile error 'Self' constrained to non-protocol type

I'm attempting to apply a constrained protocol extension to a struct (Swift 2.0) and receiving the following compiler error:

type 'Self' constrained to non-protocol type 'Foo'

struct Foo: MyProtocol {
    let myVar: String

    init(myVar: String) {
        self.myVar = myVar
    }
}

protocol MyProtocol {
    func bar()
}

extension MyProtocol where Self: Foo {
    func bar() {
        print(myVar)
    }
}

let foo = Foo(myVar: "Hello, Protocol")
foo.bar()

I can fix this error by changing struct Foo to class Foo but I don't understand why this works. Why can't I do a where Self: constrained protocol a struct?

Upvotes: 15

Views: 5690

Answers (3)

Yevhen Dubinin
Yevhen Dubinin

Reputation: 4733

As Foo is an existing type, you could simply extend it this way:

struct Foo { // <== remove MyProtocol
    let myVar: String

    init(myVar: String) {
        self.myVar = myVar
    }
}

// extending the type
extension Foo: MyProtocol  {
    func bar() {
        print(myVar)
    }
}

From The Swift Programming Language (Swift 2.2):

If you define an extension to add new functionality to an existing type, the new functionality will be available on all existing instances of that type, even if they were created before the extension was defined.

Upvotes: 0

Daniel Shin
Daniel Shin

Reputation: 5206

This is an expected behaviour considering struct are not meant to be inherited which : notation stands for.

The correct way to achieve what you described would be something like equality sign like:

extension MyProtocol where Self == Foo {
    func bar() {
        print(myVar)
    }
}

But this doesn't compile for some stupid reason like:

Same-type requirement makes generic parameter Self non-generic

For what it's worth, you can achieve the same result with the following:

protocol FooProtocol {
  var myVar: String { get }
}
struct Foo: FooProtocol, MyProtocol {
  let myVar: String
}

protocol MyProtocol {}
extension MyProtocol where Self: FooProtocol {
  func bar() {
    print(myVar)
  }
}

where FooProtocol is fake protocol which only Foo should extend.

Many third-party libraries that try to extend standard library's struct types (eg. Optional) makes use of workaround like the above.

Upvotes: 22

Brynjar
Brynjar

Reputation: 1262

I just ran into this problem too. Although I too would like a better understanding of why this is so, the Swift language reference (the guide says nothing about this) has the following from the Generic Parameters section:

Where Clauses

You can specify additional requirements on type parameters and their associated types by including a where clause after the generic parameter list. A where clause consists of the where keyword, followed by a comma-separated list of one or more requirements.

The requirements in a where clause specify that a type parameter inherits from a class or conforms to a protocol or protocol composition. Although the where clause provides syntactic sugar for expressing simple constraints on type parameters (for instance, T: Comparable is equivalent to T where T: Comparable and so on), you can use it to provide more complex constraints on type parameters and their associated types. For instance, you can express the constraints that a generic type T inherits from a class C and conforms to a protocol P as <T where T: C, T: P>.

So 'Self' cannot be a struct or emum it seems, which is a shame. Presumably there is a language design reason for this. The compiler error message could certainly be clearer though.

Upvotes: 3

Related Questions