Diffrence Between Function and Generic Function in Swift

Diffrence between Function and Generic Function in swift

Generic functions let you use the type safety of Swift on both the parameters and the result of the function to write safer, cleaner code. For example, your first function requires that both parameters passed in be of the same type, and guarantees a return value of that same type:

let minInt: Int = simpleMin(5, 12)
let minDouble: Double = simpleMin(5.0, 12.0)

whereas your second function makes no such requirements and no such guarantee:

let minOne: AnyObject = sampleMin(5, 12.0)     // minOne is an AnyObject holding an Int
let minTwo: AnyObject = sampleMin(5.0, 12) // minTwo is an AnyObject holding an Double
let minThree: AnyObject = sampleMin("Five", true) // what is supposed to happen here, exactly?

With these AnyObject results I would need to do extra checks to make sure I understand what the function returned, since AnyObject is (obviously) much less specific than my original parameters.

Moreover, generic functions allow you to put constraints on the parameters they accept, so you can make sure that the function is called only with arguments that make sense. Your first function requires that the parameters conform to the Comparable protocol, meaning that I can't just call it with two random objects. The compiler will let me call your second function with two instances of UIView, for example, and there won't be any problem until the crash when that code is executed.


Understanding protocols is an important part of using generics. A protocol defines some specific functionality that would be useful across a whole range of classes, and leaves the implementation of that functionality up to the classes themselves. The Comparable protocol above is one example; here's the definition:

protocol Comparable : Equatable {
func <=(lhs: Self, rhs: Self) -> Bool
func >=(lhs: Self, rhs: Self) -> Bool
func >(lhs: Self, rhs: Self) -> Bool
}

This is saying that any object that "conforms to" the Comparable protocol is going to have definitions for using the <=, >=, and > operators -- that is, you'd be able to write if a > b for any two objects that are both Comparable.


More reading:
The Swift Programming Language: Generics

Generic Programming - Wikipedia

Generics have long been a feature of C# and Java, among other languages, so you may find more resources to help you understand their benefits and how to use them in their documentation.

What is the in-practice difference between generic and protocol-typed function parameters?

(I realise that OP is asking less about the language implications and more about what the compiler does – but I feel it's also worthwhile also to list the general differences between generic and protocol-typed function parameters)

1. A generic placeholder constrained by a protocol must be satisfied with a concrete type

This is a consequence of protocols not conforming to themselves, therefore you cannot call generic(some:) with a SomeProtocol typed argument.

struct Foo : SomeProtocol {
var someProperty: Int
}

// of course the solution here is to remove the redundant 'SomeProtocol' type annotation
// and let foo be of type Foo, but this problem is applicable anywhere an
// 'anything that conforms to SomeProtocol' typed variable is required.
let foo : SomeProtocol = Foo(someProperty: 42)

generic(some: something) // compiler error: cannot invoke 'generic' with an argument list
// of type '(some: SomeProtocol)'

This is because the generic function expects an argument of some type T that conforms to SomeProtocol – but SomeProtocol is not a type that conforms to SomeProtocol.

A non-generic function however, with a parameter type of SomeProtocol, will accept foo as an argument:

nonGeneric(some: foo) // compiles fine

This is because it accepts 'anything that can be typed as a SomeProtocol', rather than 'a specific type that conforms to SomeProtocol'.

2. Specialisation

As covered in this fantastic WWDC talk, an 'existential container' is used in order to represent a protocol-typed value.

This container consists of:

  • A value buffer to store the value itself, which is 3 words in length. Values larger than this will be heap allocated, and a reference to the value will be stored in the value buffer (as a reference is just 1 word in size).

  • A pointer to the type's metadata. Included in the type's metadata is a pointer to its value witness table, which manages the lifetime of value in the existential container.

  • One or (in the case of protocol composition) multiple pointers to protocol witness tables for the given type. These tables keep track of the type's implementation of the protocol requirements available to call on the given protocol-typed instance.

By default, a similar structure is used in order to pass a value into a generic placeholder typed argument.

  • The argument is stored in a 3 word value buffer (which may heap allocate), which is then passed to the parameter.

  • For each generic placeholder, the function takes a metadata pointer parameter. The metatype of the type that's used to satisfy the placeholder is passed to this parameter when calling.

  • For each protocol constraint on a given placeholder, the function takes a protocol witness table pointer parameter.

However, in optimised builds, Swift is able to specialise the implementations of generic functions – allowing the compiler to generate a new function for each type of generic placeholder that it's applied with. This allows for arguments to always be simply passed by value, at the cost of increasing code size. However, as the talk then goes onto say, aggressive compiler optimisations, particularly inlining, can counteract this bloat.

3. Dispatch of protocol requirements

Because of the fact that generic functions are able to be specialised, method calls on generic arguments passed in are able to be statically dispatched (although obviously not for types that use dynamic polymorphism, such as non-final classes).

Protocol-typed functions however generally cannot benefit from this, as they don't benefit from specialisation. Therefore method calls on a protocol-typed argument will be dynamically dispatched via the protocol witness table for that given argument, which is more expensive.

Although that being said, simple protocol-typed functions may be able to benefit from inlining. In such cases, the compiler is able to eliminate the overhead of the value buffer and protocol and value witness tables (this can be seen by examining the SIL emitted in a -O build), allowing it to statically dispatch methods in the same way as generic functions. However, unlike generic specialisation, this optimisation is not guaranteed for a given function (unless you apply the @inline(__always) attribute – but usually it's best to let the compiler decide this).

Therefore in general, generic functions are favoured over protocol-typed functions in terms of performance, as they can achieve static dispatch of methods without having to be inlined.

4. Overload resolution

When performing overload resolution, the compiler will favour the protocol-typed function over the generic one.

struct Foo : SomeProtocol {
var someProperty: Int
}

func bar<T : SomeProtocol>(_ some: T) {
print("generic")
}

func bar(_ some: SomeProtocol) {
print("protocol-typed")
}

bar(Foo(someProperty: 5)) // protocol-typed

This is because Swift favours an explicitly typed parameter over a generic one (see this Q&A).

5. Generic placeholders enforce the same type

As already said, using a generic placeholder allows you to enforce that the same type is used for all parameters/returns that are typed with that particular placeholder.

The function:

func generic<T : SomeProtocol>(a: T, b: T) -> T {
return a.someProperty < b.someProperty ? b : a
}

takes two arguments and has a return of the same concrete type, where that type conforms to SomeProtocol.

However the function:

func nongeneric(a: SomeProtocol, b: SomeProtocol) -> SomeProtocol {
return a.someProperty < b.someProperty ? b : a
}

carries no promises other than the arguments and return must conform to SomeProtocol. The actual concrete types that are passed and returned do not necessarily have to be the same.

Difference between using Generic and Protocol as type parameters, what are the pros and cons of implement them in a function

There is actually a video from this year's WWDC about that (it was about performance of classes, structs and protocols; I don't have a link but you should be able to find it).

In your second function, where you pass a any value that conforms to that protocol, you are actually passing a container that has 24 bytes of storage for the passed value, and 16 bytes for type related information (to determine which methods to call, ergo dynamic dispatch). If the passed value is now bigger than 24 bytes in memory, the object will be allocated on the heap and the container stores a reference to that object! That is actually extremely time consuming and should certainly be avoided if possible.

In your first function, where you use a generic constraint, there is actually created another function by the compiler that explicitly performs the function's operations upon that type. (If you use this function with lots of different types, your code size may, however, increase significantly; see C++ code bloat for further reference.) However, the compiler can now statically dispatch the methods, inline the function if possible and does certainly not have to allocate any heap space. Stated in the video mentioned above, code size does not have to increase significantly as code can still be shared... so the function with generic constraint is certainly the way to go!

Swift Generic Function Types Ordering

You need to provide the type information at the call site, consider this two structs :

struct AHolder: A {

}
struct BHolder: B {

}

and let's say that your foo methods only print:

func foo<AHolder: A>(of: AHolder) {
print("AHolder")
}

func foo<BHolder: B>(of: BHolder) {
print("BHolder")
}

func foo(of: C) {
print("C")
}

this works fine :

foo(of: C()) // prints "C"
foo(of: AHolder()) // prints "AHolder"
foo(of: BHolder()) // prints "BHolder"

Why? because at the call site the compiler knows that you want to print an instance of C, an instance of AHolder and an instance of BHolder.

now try this :

let a: some A = BHolder()
foo(of: a) // prints "AHolder"
let b: some B = C()
foo(of: b) // prints "BHolder"

At the call site, all the compiler knows is that a is an object of some type conforming to A, and b an object of some type conforming to B, so even if the variables are really instances of BHolder and C, the compiler does not know that and uses the overloads that match the types it knows ie. foo<AHolder: A>(of:) and foo<BHolder: B>(of:).

What is the difference between passing classes as generic types or types?

In your overly simplified example, there's not much difference.

However, as soon as your type constraints become more complex, the only way to achieve the desired interface is via generics.

A great example if when you want a function to take an input argument whose type is a protocol with an associated type, such as SwiftUI's View.

If you tried to use View as the input argument type, you'd get a compile time error

func modifyView(_ view: View) {

}

Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements

However, as soon as you make view generic with a type constraint on View, the code compiles.

func modifyView<V: View>(_ view: V) {

}

You don't have to look at custom functions, the standard library is full of generic functions as well. JSONDecoder.decode showcases another common use case for generics, when you want your function to return a specific type based on some input arguments. This enables the developer to only write the function 1x, but make it work on lots of different types, while keeping type safety.

func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T

The difference between an any type and a generic type in swift

I'm not going to explain generics in details and i'll just point out the essential differences.

In the first example, you'll be able to append any type in that array, without being able to restrict beforehand your array to a specific type and to leverage compile time checks to guarantee that the array will not contain extraneous types. Not much to see in that example.

The second example contains instead a generic function that provides all of the above functionalities, consistency checks on the content of the array will come for free and if you want you'll also be able to specify additional characteristics of that generic type T, like requesting that it implements a specific protocol (e.g. limit duplicate() to object that implement Comparable or Equatable).

But that is just a simple example of a generic function, you can also have parametrized classes (what you'll use the most) and there are a lot of additional functionalities.

Never use Any as a poor-man generics, real generics are way more flexible, add useful checks and make more explicit your intentions, with minimal additional effort required to implement them.

What's the difference between using or not using the 'where' clause with generics?

This is clearly stated in the Swift guide:

The requirements in a generic where clause specify that a type
parameter inherits from a class or conforms to a protocol or protocol
composition. Although the generic 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 constrain
the associated types of type parameters to conform to protocols. For
example, <S: Sequence> where S.Iterator.Element: Equatable specifies
that S conforms to the Sequence protocol and that the associated type
S.Iterator.Element conforms to the Equatable protocol. This constraint
ensures that each element of the sequence is equatable.

Simply put, where lets you specify constraints about the associated types of a generic parameter, while in <> you can't do this.

Is there a practical difference between a type constraint on a generic type directly vs using a 'where' clause?

There is no difference. The first form

func testX<T>(value: T) where T: StringProtocol

was introduced with SE-0081 Move where clause to end of declaration to increase readability, in particular for longer lists of constraints. The rationale was to remove the where clause out of the generic parameter list, for example

func foo<S: Sequence where S.Element == Int>(seq: S)

became

func foo<S: Sequence>(seq: S) where S.Element == Int

in Swift 3. As a side-effect, even simple constraints such as
your T: StringProtocol can be moved to the newly introduced where-clause.



Related Topics



Leave a reply



Submit