Swift uses the wrong equality function to determine if an array contains an object [duplicate] - swift

I have a custom operator defined globally like so:
func ==(lhs: Item!, rhs: Item!)->Bool {
return lhs?.dateCreated == rhs?.dateCreated
}
And if I execute this code:
let i1 = Item()
let i2 = Item()
let date = Date()
i1.dateCreated = date
i2.dateCreated = date
let areEqual = i1 == i2
areEqual is false. In this case I know for sure that my custom operator is not firing. However, if I add this code into the playground:
//same function
func ==(lhs: Item!, rhs: item!)->Bool {
return lhs?.dateCreated == rhs?.dateCreated
}
//same code
let i1 = Item()
let i2 = Item()
let date = Date()
i1.dateCreated = date
i2.dateCreated = date
let areEqual = i1 == i2
areEqual is true -- I'm assuming my custom operator is fired in this case.
I have no other custom operators defined that would cause a conflict in the non-playground case, and the Item class is the same in both cases, so why is my custom operator not being called outside the playground?
The Item class inherits from the Object class provided by Realm, which eventually inherits from NSObject. I also noticed that if I define non-optional inputs for the overload, when the inputs are optionals it's not fired.

There are two main problems with what you're trying to do here.
1. Overload resolution favours supertypes over optional promotion
You've declared your == overload for Item! parameters rather than Item parameters. By doing so, the type checker is weighing more in favour of statically dispatching to NSObject's overload for ==, as it appears that the type checker favours subclass to superclass conversions over optional promotion (I haven't been able to find a source to confirm this though).
Usually, you shouldn't need to define your own overload to handle optionals. By conforming a given type to Equatable, you'll automatically get an == overload which handles equality checking between optional instances of that type.
A simpler example that demonstrates the favouring of a superclass overload over an optional subclass overload would be:
// custom operator just for testing.
infix operator <===>
class Foo {}
class Bar : Foo {}
func <===>(lhs: Foo, rhs: Foo) {
print("Foo's overload")
}
func <===>(lhs: Bar?, rhs: Bar?) {
print("Bar's overload")
}
let b = Bar()
b <===> b // Foo's overload
If the Bar? overload is changed to Bar – that overload will be called instead.
Therefore you should change your overload to take Item parameters instead. You'll now be able to use that overload to compare two Item instances for equality. However, this won't fully solve your problem due to the next issue.
2. Subclasses can't directly re-implement protocol requirements
Item doesn't directly conform to Equatable. Instead, it inherits from NSObject, which already conforms to Equatable. Its implementation of == just forwards onto isEqual(_:) – which by default compares memory addresses (i.e checks to see if the two instances are the exact same instance).
What this means is that if you overload == for Item, that overload is not able to be dynamically dispatched to. This is because Item doesn't get its own protocol witness table for conformance to Equatable – it instead relies on NSObject's PWT, which will dispatch to its == overload, simply invoking isEqual(_:).
(Protocol witness tables are the mechanism used in order to achieve dynamic dispatch with protocols – see this WWDC talk on them for more info.)
This will therefore prevent your overload from being called in generic contexts, including the aforementioned free == overload for optionals – explaining why it doesn't work when you attempt to compare Item? instances.
This behaviour can be seen in the following example:
class Foo : Equatable {}
class Bar : Foo {}
func ==(lhs: Foo, rhs: Foo) -> Bool { // gets added to Foo's protocol witness table.
print("Foo's overload") // for conformance to Equatable.
return true
}
func ==(lhs: Bar, rhs: Bar) -> Bool { // Bar doesn't have a PWT for conformance to
print("Bar's overload") // Equatable (as Foo already has), so cannot
return true // dynamically dispatch to this overload.
}
func areEqual<T : Equatable>(lhs: T, rhs: T) -> Bool {
return lhs == rhs // dynamically dispatched via the protocol witness table.
}
let b = Bar()
areEqual(lhs: b, rhs: b) // Foo's overload
So, even if you were to change your overload such that it takes an Item input, if == was ever called from a generic context on an Item instance, your overload won't get called. NSObject's overload will.
This behaviour is somewhat non-obvious, and has been filed as a bug – SR-1729. The reasoning behind it, as explained by Jordan Rose is:
[...] The subclass does not get to provide new members to satisfy the conformance. This is important because a protocol can be added to a base class in one module and a subclass created in another module.
Which makes sense, as the module in which the subclass resides would have to be recompiled in order to allow it to satisfy the conformance – which would likely result in problematic behaviour.
It's worth noting however that this limitation is only really problematic with operator requirements, as other protocol requirements can usually be overridden by subclasses. In such cases, the overriding implementations are added to the subclass' vtable, allowing for dynamic dispatch to take place as expected. However, it's currently not possible to achieve this with operators without the use of a helper method (such as isEqual(_:)).
The Solution
The solution therefore is to override NSObject's isEqual(_:) method and hash property rather than overloading == (see this Q&A for how to go about this). This will ensure that your equality implementation will always be called, regardless of the context – as your override will be added to the class' vtable, allowing for dynamic dispatch.
The reasoning behind overriding hash as well as isEqual(_:) is that you need to maintain the promise that if two objects compare equal, their hashes must be the same. All sorts of weirdness can occur otherwise, if an Item is ever hashed.
Obviously, the solution for non-NSObject derived classes would be to define your own isEqual(_:) method, and have subclasses override it (and then just have the == overload chain to it).

Related

Why don't protocols in Swift use brackets instead of associated types? [duplicate]

I'm confused about the difference between the syntax used for associated types for protocols, on the one hand, and generic types on the other.
In Swift, for example, one can define a generic type using something like
struct Stack<T> {
var items = [T]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}
while one defines a protocol with associated types using something like
protocol Container {
associatedtype T
mutating func append(item: T)
var count: Int { get }
subscript(i: Int) -> T { get }
}
Why isn't the latter just:
protocol Container<T> {
mutating func append(item: T)
var count: Int { get }
subscript(i: Int) -> T { get }
}
Is there some deep (or perhaps just obvious and lost on me) reason that the language hasn't adopted the latter syntax?
RobNapier's answer is (as usual) quite good, but just for an alternate perspective that might prove further enlightening...
On Associated Types
A protocol is an abstract set of requirements — a checklist that a concrete type must fulfill in order to say it conforms to the protocol. Traditionally one thinks of that checklist of being behaviors: methods or properties implemented by the concrete type. Associated types are a way of naming the things that are involved in such a checklist, and thereby expanding the definition while keeping it open-ended as to how a conforming type implements conformance.
When you see:
protocol SimpleSetType {
associatedtype Element
func insert(_ element: Element)
func contains(_ element: Element) -> Bool
// ...
}
What that means is that, for a type to claim conformance to SimpleSetType, not only must that type contain insert(_:) and contains(_:) functions, those two functions must take the same type of parameter as each other. But it doesn't matter what the type of that parameter is.
You can implement this protocol with a generic or non-generic type:
class BagOfBytes: SimpleSetType {
func insert(_ byte: UInt8) { /*...*/ }
func contains(_ byte: UInt8) -> Bool { /*...*/ }
}
struct SetOfEquatables<T: Equatable>: SimpleSetType {
func insert(_ item: T) { /*...*/ }
func contains(_ item: T) -> Bool { /*...*/ }
}
Notice that nowhere does BagOfBytes or SetOfEquatables define the connection between SimpleSetType.Element and the type used as the parameter for their two methods — the compiler automagically works out that those types are associated with the right methods, so they meet the protocol's requirement for an associated type.
On Generic Type Parameters
Where associated types expand your vocabulary for creating abstract checklists, generic type parameters restrict the implementation of a concrete type. When you have a generic class like this:
class ViewController<V: View> {
var view: V
}
It doesn't say that there are lots of different ways to make a ViewController (as long as you have a view), it says a ViewController is a real, concrete thing, and it has a view. And furthermore, we don't know exactly what kind of view any given ViewController instance has, but we do know that it must be a View (either a subclass of the View class, or a type implementing the View protocol... we don't say).
Or to put it another way, writing a generic type or function is sort of a shortcut for writing actual code. Take this example:
func allEqual<T: Equatable>(a: T, b: T, c: T) {
return a == b && b == c
}
This has the same effect as if you went through all the Equatable types and wrote:
func allEqual(a: Int, b: Int, c: Int) { return a == b && b == c }
func allEqual(a: String, b: String, c: String) { return a == b && b == c }
func allEqual(a: Samophlange, b: Samophlange, c: Samophlange) { return a == b && b == c }
As you can see, we're creating code here, implementing new behavior — much unlike with protocol associated types where we're only describing the requirements for something else to fulfill.
TLDR
Associated types and generic type parameters are very different kinds of tools: associated types are a language of description, and generics are a language of implementation. They have very different purposes, even though their uses sometimes look similar (especially when it comes to subtle-at-first-glance differences like that between an abstract blueprint for collections of any element type, and an actual collection type that can still have any generic element). Because they're very different beasts, they have different syntax.
Further reading
The Swift team has a nice writeup on generics, protocols, and related features here.
This has been covered a few times on the devlist. The basic answer is that associated types are more flexible than type parameters. While you have a specific case here of one type parameter, it is quite possible to have several. For instance, Collections have an Element type, but also an Index type and a Generator type. If you specialized them entirely with type parameterization, you'd have to talk about things like Array<String, Int, Generator<String>> or the like. (This would allow me to create arrays that were subscripted by something other than Int, which could be considered a feature, but also adds a lot of complexity.)
It's possible to skip all that (Java does), but then you have fewer ways that you can constrain your types. Java in fact is pretty limited in how it can constrain types. You can't have an arbitrary indexing type on your collections in Java. Scala extends the Java type system with associated types just like Swift. Associated types have been incredibly powerful in Scala. They are also a regular source of confusion and hair-tearing.
Whether this extra power is worth it is a completely different question, and only time will tell. But associated types definitely are more powerful than simple type parameterization.
To add to the already great answers, there's another big difference between generics and associated types: the direction of the type generic fulfilment.
In case of generic types, it's the client that dictates which type should be used for the generic, while in case of protocols with associated types that's totally in the control of the type itself. Which means that types that conform to associated types are in liberty to choose the associated type that suits them best, instead of being forced to work with some types they don't know about.
As others have said, the Collection protocol is a good example of why associated types are more fit in some cases. The protocol looks like this (note that I omitted some of the other associated types):
protocol Collection {
associatedtype Element
associatedtype Index
...
}
If the protocol would've been defined as Collection<Element, Index>, then this would've put a great burden on the type conforming to Collection, as it would've have to support any kind of indexing, many of them which don't even make sense (e.g. indexing by a UIApplication value).
So, choosing the associated types road for protocol generics it's also a matter of empowering the type that conforms to that protocol, since it's that type the one that dictates what happens with the generics. And yes, that might sound less flexible, but if you think about it all types that conform to Collection are generic types, however they only allow generics for the types that make sense (i.e. Element), while "hardcoding" the other associated types (e.g. Index) to types that make sense and are usable in their context.

Swift Generics - return equals if they are Equatable, or return nil

Is there a way to check if a generic type conforms to Equatable or not? I would like to be able to check if two objects of the same generic type are equal or not, or if equality has no meaning for them.
As Equatable can only be used as a generic constraint (because it has Self or associatedType requirements) I have tried using generic overloading:
//If T is equatable, this is more specific so should be called
func equals<T:Equatable>(lhs:T, rhs:T) -> Bool?{
return lhs == rhs
}
//This should only be called if T is not equatable at compile time
func equals<T>(lhs:T, rhs:T) -> Bool?{
return nil
}
This works when called with a specific type, e.g. equals(lhs:1, rhs:1) returns true as expected. However, if it is called in a generic context it always returns nil:
func doSomethingThenCheckEquals<T>(lhs:T, rhs:T){
//Do something here which has no type requirements
//Check if the two objects are equal - would usually do something with the result
//This will always use equals<T> and never equals<T:Equatable>, so will always be nil
_ = equals(lhs:lhs, rhs:rhs)
}
Is there some way to achieve the desired result?
Further, according to this answer, the compiler starts with a single implementation with dynamic type checking but can in some cases create specialized implementations. In the case that the compiler created a specialized implementation, would it behave similarly to the first case (where equals(lhs:1, rhs:1) returns true)?
The compiler is doing its job as expected. The second method which you have declared is independent of the first and so it doesn't know anything about T.
Generic is forward declaration which means we need to tell the compiler what protocol it will follow and then the compiler will take all necessary step to fit this in. Maybe in future, we can expect function call level type interpretation but currently, it is not available.
func doSomethingThenCheckEquals<T>(lhs:T, rhs:T){
//Do something here which has no type requirements
//Check if the two objects are equal - would usually do something with the result
//This will always use equals<T> and never equals<T:Equatable>, so will always be nil
_ = equals(lhs:lhs, rhs:rhs)
}
The best solution would be to use where clause.
func doSomethingThenCheckEqual<T>(lhs: T, rhs: T) where T:Equatable {
}
Read more about it here.

Identity operator can also check whether named types are the same rather only than reference equaliy

As far as I understand, identity operator is used to determine if two objects have the same reference. It means, in practice, the both side of the operator shall be an object.
However, I've tried the following code and it confused me of the identity operator's function of what I understand
class Dog {}
let d: Dog = Dog()
if type(of: d) === Dog.self {
print("yep") //prints out "yep"
}
if type(of: d) == Dog.self {
print("yep") //prints out "yep"
}
The left and right side of the identity operator is not an object but a type and it seems, for this point, semantic equivalence operator and object identity operator (looks like) works in the same way.
Question:
Is this a bug or I didn't get the whole point correctly.
Thanks for your help and time
The left and right side of the identity operator is not an object but a type.
Actually, on Apple platforms, they are objects.
This is due to the fact that Swift classes are implemented as Objective-C classes under the hood, as Mike Ash goes into detail in this great blog post. This means that the metatype of a class is also an Objective-C class, and therefore conforms to AnyObject.
Because of this, you can compare class metatypes with the identity operator, as it's defined as:
public func ===(lhs: AnyObject?, rhs: AnyObject?) -> Bool
It'll compare whether the two objects are the same object, or specifically in this case, the same class metatype.
In contrast, under the hood, the metatype for a value type isn't an Objective-C object – it's just a pointer to some static metadata. If we rewrite your example to use a struct:
struct Dog {}
let d = Dog()
// Binary operator '===' cannot be applied to two 'Dog.Type' operands
if type(of: d) === Dog.self {
print("yep")
}
You'll see that we can no longer use === to compare metatypes, as they don't conform to AnyObject. So really, the ability to use the identity operator to compare class metatypes is just a side-effect of them being implemented as Objective-C objects.
The universal way of comparing metatypes is with the equality operator ==, as Swift provides an overload specifically for metatypes:
public func ==(t0: Any.Type?, t1: Any.Type?) -> Bool
This checks whether two metatypes are the same, however unlike ===, it works with both class metatypes and value-typed metatypes. Under the hood, it's implemented as a simple pointer comparison, so should always yield identical results as === with class metatypes.
I would therefore always recommend comparing metatypes with ==, as you're not relying on the conformance to AnyObject. For example, on a Linux platform, class metatypes don't conform to AnyObject and therefore cannot be compared with the identity operator (although interestingly, when Foundation is imported, it appears to add a === overload for AnyObject.Type operands – presumably to aid interoperability).
Not a bug, they are the same thing.
Dog, as a class (type) is a singular, there can only be one. There can be many Instances but only one of the Class.
type(of: d) return the owner Class of d, Dog.self returns the Class itself. They are exactly the same object, the singular Class of Dog.

== overload for custom class is not always called

I have a custom operator defined globally like so:
func ==(lhs: Item!, rhs: Item!)->Bool {
return lhs?.dateCreated == rhs?.dateCreated
}
And if I execute this code:
let i1 = Item()
let i2 = Item()
let date = Date()
i1.dateCreated = date
i2.dateCreated = date
let areEqual = i1 == i2
areEqual is false. In this case I know for sure that my custom operator is not firing. However, if I add this code into the playground:
//same function
func ==(lhs: Item!, rhs: item!)->Bool {
return lhs?.dateCreated == rhs?.dateCreated
}
//same code
let i1 = Item()
let i2 = Item()
let date = Date()
i1.dateCreated = date
i2.dateCreated = date
let areEqual = i1 == i2
areEqual is true -- I'm assuming my custom operator is fired in this case.
I have no other custom operators defined that would cause a conflict in the non-playground case, and the Item class is the same in both cases, so why is my custom operator not being called outside the playground?
The Item class inherits from the Object class provided by Realm, which eventually inherits from NSObject. I also noticed that if I define non-optional inputs for the overload, when the inputs are optionals it's not fired.
There are two main problems with what you're trying to do here.
1. Overload resolution favours supertypes over optional promotion
You've declared your == overload for Item! parameters rather than Item parameters. By doing so, the type checker is weighing more in favour of statically dispatching to NSObject's overload for ==, as it appears that the type checker favours subclass to superclass conversions over optional promotion (I haven't been able to find a source to confirm this though).
Usually, you shouldn't need to define your own overload to handle optionals. By conforming a given type to Equatable, you'll automatically get an == overload which handles equality checking between optional instances of that type.
A simpler example that demonstrates the favouring of a superclass overload over an optional subclass overload would be:
// custom operator just for testing.
infix operator <===>
class Foo {}
class Bar : Foo {}
func <===>(lhs: Foo, rhs: Foo) {
print("Foo's overload")
}
func <===>(lhs: Bar?, rhs: Bar?) {
print("Bar's overload")
}
let b = Bar()
b <===> b // Foo's overload
If the Bar? overload is changed to Bar – that overload will be called instead.
Therefore you should change your overload to take Item parameters instead. You'll now be able to use that overload to compare two Item instances for equality. However, this won't fully solve your problem due to the next issue.
2. Subclasses can't directly re-implement protocol requirements
Item doesn't directly conform to Equatable. Instead, it inherits from NSObject, which already conforms to Equatable. Its implementation of == just forwards onto isEqual(_:) – which by default compares memory addresses (i.e checks to see if the two instances are the exact same instance).
What this means is that if you overload == for Item, that overload is not able to be dynamically dispatched to. This is because Item doesn't get its own protocol witness table for conformance to Equatable – it instead relies on NSObject's PWT, which will dispatch to its == overload, simply invoking isEqual(_:).
(Protocol witness tables are the mechanism used in order to achieve dynamic dispatch with protocols – see this WWDC talk on them for more info.)
This will therefore prevent your overload from being called in generic contexts, including the aforementioned free == overload for optionals – explaining why it doesn't work when you attempt to compare Item? instances.
This behaviour can be seen in the following example:
class Foo : Equatable {}
class Bar : Foo {}
func ==(lhs: Foo, rhs: Foo) -> Bool { // gets added to Foo's protocol witness table.
print("Foo's overload") // for conformance to Equatable.
return true
}
func ==(lhs: Bar, rhs: Bar) -> Bool { // Bar doesn't have a PWT for conformance to
print("Bar's overload") // Equatable (as Foo already has), so cannot
return true // dynamically dispatch to this overload.
}
func areEqual<T : Equatable>(lhs: T, rhs: T) -> Bool {
return lhs == rhs // dynamically dispatched via the protocol witness table.
}
let b = Bar()
areEqual(lhs: b, rhs: b) // Foo's overload
So, even if you were to change your overload such that it takes an Item input, if == was ever called from a generic context on an Item instance, your overload won't get called. NSObject's overload will.
This behaviour is somewhat non-obvious, and has been filed as a bug – SR-1729. The reasoning behind it, as explained by Jordan Rose is:
[...] The subclass does not get to provide new members to satisfy the conformance. This is important because a protocol can be added to a base class in one module and a subclass created in another module.
Which makes sense, as the module in which the subclass resides would have to be recompiled in order to allow it to satisfy the conformance – which would likely result in problematic behaviour.
It's worth noting however that this limitation is only really problematic with operator requirements, as other protocol requirements can usually be overridden by subclasses. In such cases, the overriding implementations are added to the subclass' vtable, allowing for dynamic dispatch to take place as expected. However, it's currently not possible to achieve this with operators without the use of a helper method (such as isEqual(_:)).
The Solution
The solution therefore is to override NSObject's isEqual(_:) method and hash property rather than overloading == (see this Q&A for how to go about this). This will ensure that your equality implementation will always be called, regardless of the context – as your override will be added to the class' vtable, allowing for dynamic dispatch.
The reasoning behind overriding hash as well as isEqual(_:) is that you need to maintain the promise that if two objects compare equal, their hashes must be the same. All sorts of weirdness can occur otherwise, if an Item is ever hashed.
Obviously, the solution for non-NSObject derived classes would be to define your own isEqual(_:) method, and have subclasses override it (and then just have the == overload chain to it).

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

Given a protocol without any associated types:
protocol SomeProtocol
{
var someProperty: Int { get }
}
What is the difference between these two functions, in practice (meaning not "one is generic and the other is not")? Do they generate different code, do they have different runtime characteristics? Do these differences change when the protocol or functions become non-trivial? (since a compiler could probably inline something like this)
func generic<T: SomeProtocol>(some: T) -> Int
{
return some.someProperty
}
func nonGeneric(some: SomeProtocol) -> Int
{
return some.someProperty
}
I'm mostly asking about differences in what the compiler does, I understand the language-level implications of both. Basically, does nonGeneric imply a constant code size but slower dynamic dispatch, vs. generic using a growing code size per type passed, but with fast static dispatch?
(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.
If your generic method had more than one parameter involving T, there would be a difference.
func generic<T: SomeProtocol>(some: T, someOther: T) -> Int
{
return some.someProperty
}
In the method above, some and someOther have to be the same type. They can be any type that conforms to SomeProtocol, but they have to be the same type.
However, without generics:
func nonGeneric(some: SomeProtocol, someOther: SomeProtocol) -> Int
{
return some.someProperty
}
some and someOther can be different types, as long as they conform to SomeProtocol.