What is the preferred way to declare a type parameter of a function?

generics

(Lincoln Wu) #1

Hello, community,
I want to write a function/method with a type parameter, specifically when the input type have constraints like: T: A, in which A is a class type or a non-PAT protocol type, I have 2 options.

func foo<T: A>(_ type: T.Type, ...)   // 1
func foo(_ type: A.Type, ...)  // 2

I can invoke either with foo(MyClass.self, ...)

Which is the preferred way in Swift?
Is the former one more efficient in some circumstances?


(Tomáš Znamenáček) #2

I’ve tried to generate intermediate SIL code and LLVM IR code based on this great post:

Source:

protocol P {}
func foo<T: P>(_ type: T.Type) {}

protocol P {}
func foo(_ type: P.Type) {}

SIL:

// foo<A>(_:)
sil hidden @$S3one3fooyyxmAA1PRzlF : $@convention(thin) <T where T : P> (@thick T.Type) -> () {
// %0                                             // user: %1
bb0(%0 : $@thick T.Type):
  debug_value %0 : $@thick T.Type, let, name "type", argno 1 // id: %1
  %2 = tuple ()                                   // user: %3
  return %2 : $()                                 // id: %3
} // end sil function '$S3one3fooyyxmAA1PRzlF'

// foo(_:)
sil hidden @$S3one3fooyyAA1P_pXpF : $@convention(thin) (@thick P.Type) -> () {
// %0                                             // user: %1
bb0(%0 : $@thick P.Type):
  debug_value %0 : $@thick P.Type, let, name "type", argno 1 // id: %1
  %2 = tuple ()                                   // user: %3
  return %2 : $()                                 // id: %3
} // end sil function '$S3one3fooyyAA1P_pXpF'

And the LLVM IR:

// foo<T>(_:)
define hidden swiftcc void @"$S3one3fooyyxmAA1PRzlF"(%swift.type*, %swift.type* %T, i8** %T.P) #0 {
entry:
  ret void
}

// foo(_:)
define hidden swiftcc void @"$S3one3fooyyAA1P_pXpF"(%swift.type*, i8**) #0 {
entry:
  ret void
}

The result is that this is all fine & nerdy, but I still don’t know the answer.


(Davide De Franceschi) #3

I think that the generic one is more performant but has bigger binary size (but both are probably not relevant in a common use case). At the semantic level they're very similar* if you only need to work with the given interface and not on memory-level details.

* should be the difference between "this T type conforming to P" and "a type conforming to P"


(Quinn “The Eskimo!”) #4

should be the difference between "this T type conforming to P" and
"a type conforming to P"

This matters sometimes. For example, in option 1 you can use T on the right side of an is operator.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple


(Davide De Franceschi) #5

Yeah the main difference is whether the code can use static knowledge about the type or has only access to runtime details. But with "you can use T on the right side of an is operator", don't you mean something like this?

protocol P {}
struct T: P {}

let t: P = T()
if t is T { print("T") }

Because is is a runtime check so it can be done with an existential as well