Why do I get the error “Protocol … can only be use

2020-02-11 07:26发布

问题:

I wrote an extension onto Int as below.

extension Int {
    func squared () -> Int {
        return self * self
    }
}

print(10.squared()) // works

The above code works. Now I want to extend the IntegerType protocol so that Int, UInt, Int64, etc would all conform. My code is as below.

extension IntegerType {

    func squared () -> IntegerType { // this line creates error

        return self * self

    }
}

I get error:

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

I already saw this question and its video & this question, still couldn't understand. I only understood that there is some associatedType which in this case is Self but couldn't connect the dots. I feel like also my lack of knowledge on the Generics subject is also a reason...

Can someone elaborate a bit on the subject and why does the extension create an error?

回答1:

A function return type can only be a concrete Type.

The point is Type. Anything struct, class or Protocols that are completely defined in themselves are pure Type. However when a protocol or struct depend on another Generic Type Placeholder such as T, then this is a partial type.

Type are a data construct that compiler has to allocate certain memory.

So something like this:

let a = Array<T>() or let b = T is not sufficient information for the compiler to deduce at compile time.

Hence, this wont work.

  extension IntegerType {

    func squared () -> IntegerType { // this line creates error

        return self * self

    }
}

Here, IntegerType is a partial type. It is a generic protocol that only when conformed can then we know the exact type. Similar to Array. Array itself is not a type. Its a generic container. Only when someone creates it with Array() or Array()... then it has a type.

The same happened with you.

public protocol IntegerType : _IntegerType, RandomAccessIndexType {

then again,

public protocol RandomAccessIndexType : BidirectionalIndexType, Strideable, _RandomAccessAmbiguity {
@warn_unused_result
    public func advancedBy(n: Self.Distance) -> Self

then again,

   public protocol _RandomAccessAmbiguity {
    associatedtype Distance : _SignedIntegerType = Int
   }

Hence, as RandomAccessIndexType has Self requirement meaning until and unless someone conforms to it, Self is unknown placeholder. It is partial Type.

Since IntegerType conforms to the RandomAccessIndexType and _RandomAccessAmbuiguity which requires Distance associated type too.

Hence you cant do this too

let a: IntegerType = 12

Again IntegerType needs to know Self and Distance (associatedType).

Int however provides the details like so

public struct Int : SignedIntegerType, Comparable, Equatable {
    /// A type that can represent the number of steps between pairs of
    /// values.
    public typealias Distance = Int

Hence you can do such

let a:Int = 10

because it provides Self for SignedIntegerType and Distance for its other counterpart.

Simply put it:

A partial type cannot be used where a concrete type can be. A partial type are good for other generics and constraining them.



回答2:

You just have to return Self

edit/update:

Note: You can extend all numeric types (Integer & FloatingPoint) in Swift 4 extending the Numeric Protocol

Swift 4

extension Numeric {
    func squared() -> Self {
        return self * self
    }
}

Swift 3

extension Integer {
    func squared() -> Self { 
        return self * self
    }
}