1

What advantages are there to using generics with a where clause over specifying a protocol for an argument, as in the following function signatures?

func encode<T>(_ value: T) throws -> Data where T : Encodable {...}
func encode(value: Encodable) throws -> Data {...}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
GoldenJoe
  • 7,874
  • 7
  • 53
  • 92

2 Answers2

1

The first is a generic method that requires a concrete type that conforms to Encodable. That means for each call to encode with a different type, a completely new copy of the function may be created, optimized just for that concrete type. In some cases the compiler may remove some of these copies, but in principle encode<Int>() is a completely different function than encode<String>(). It's a (generic) system for creating functions at compile time.

In contrast, the second is a non-generic function that accepts a parameter of the "Encodable existential" type. An existential is a compiler-generated box that wraps some other type. In principle this means that the value will be copied into the box at run time before being passed, possibly requiring a heap allocation if it's too large for the box (again, it may not be because the compiler is very smart and can sometimes see that it's unnecessary).

This ambiguity between the name of the protocol and the name of the existential will hopefully be fixed in the future (and there's discussion about doing so). In the future, the latter function will hopefully be spelled (note "any"):

func encode(value: any Encodable) throws -> Data {...}

The former might be faster. It might also take more space for all the copies of the function. (But see above about the compiler. Do not assume you know which of these will be faster in an actual, optimized build.)

The former provides a real, concrete type. That means it can be used for things that require a real, concrete type, such as calling a static method, or init. This means it can be used when the protocol has an associated type.

The latter is boxed into an existential, meaning it can be stored into heterogeneous collections. The former can only be put into collections of its particular concrete type.

So they're pretty different things, and each has its purpose.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
0

You can use multiple type constraints.

func encode<T>(encodable: T) -> Data where T: Encodable, T: Decodable {
    ...
}
Alex
  • 3,861
  • 5
  • 28
  • 44