altralaser
altralaser

Reputation: 2073

More than one protocol in a type constraint

I want to use a generic typed class and a type constraint:

class MyCustomClass<T : Equatable> {
  var a: Array<T>
  init() {
    a = Array<T>()
  }
}

That works fine. But what happens if I want to use a second protocol, f.e.

class MyCustomClass<T : Equatable, IndexableBase> {
  var a: Array<T>
  init() {
    a = Array<T>()
  }
}

It says that the initializer fails because I have to use 2 instead of 1 argument. That I don't understand.

Upvotes: 7

Views: 2781

Answers (2)

Franco Meloni
Franco Meloni

Reputation: 156

you can use this workaround

class MyCustomClass<T: Equatable where T: IndexableBase > {
    var a: Array<T>
    init() {
        a = Array<T>()
    }
}

Swift 4:

class MyCustomClass<T: Equatable> where T: Collection  {
    var a: Array<T>
    init() {
        a = Array<T>()
    }
}

Upvotes: 2

dfrib
dfrib

Reputation: 73176

Swift 3

(I've replaced IndexableBase with Collection in your example, you should prefer the latter)


As per Swift 3, protocol composition use the infix operator & over the previous protocol<...> construct, as described in the accepted and implemented evolution proposal:

Hence, using protocol composition you may place the combined type constraint at the T placeholder in the parameter list:

class MyCustomClass<T: Equatable & Collection> { /* ... */ }

As an alternative to protocol composition, you could also make use of where clause to join several type (and subtype) constraint. As per the accepted and implemented evolution proposal:

the where clause has been moved to end of the declaration, in which case your example would read:

class MyCustomClass<T: Equatable> where T: Comparable { /* ... */ }

or, even place the full protocol composition with the where clause at the end of the declaration

class MyCustomClass<T> where T: Equatable & Comparable { /* ... */ }

What style should we prefer?

The interesting discussion is what we should consider "best practice", as this particular subject is not specified Swift API guidelines. Do we place

  • all (main type) constraints in the parameter list, or
  • all constraints at the end of the declaration, or
  • split up constraints with some in the parameter list and others placed at the end of the declaration?

Consider the following contrived example, where we have a construct a protocol that we plan to use as a type constraint (Doable), which in itself holds an associatedtype to which we may place a type constraint.

protocol Doable { 
    associatedtype U
}

Using the different methods above, all the following three alternatives are valid, syntactically

// alternative #1
func foo<T: Equatable & Doable>(_ bar: T) -> () where T.U: Comparable { /* ... */ }

// alternative #2
func foo<T: Equatable>(_ bar: T) -> () where T: Doable, T.U: Comparable { /* ... */ }

// alternative #3
func foo<T>(_ bar: T) -> () where T: Equatable & Doable, T.U: Comparable { /* ... */ }

I'll quote Apple dev Joe Groff from the evolution thread that that brought forth SE-0081:

It's a judgment call. It's my feeling that in many cases, a generic parameter is constrained by at least one important protocol or base class that's worth calling out up front, so it's reasonable to allow things like

func foo<C: Collection>(x: C) -> C.Element 

without banishing the Collection constraint too far from the front of the declaration.

So in the contrived example above, it might be appropriate to use protocol composition for the type constraints that apply directly to the generic placeholder T, and place these constraints in the parameter list, whereas the subtype constraint of T.U is placed at the end of the declaration. Namely, alternative #1 above.

Upvotes: 17

Related Questions