Search code examples
swiftgenericsprotocols

Swift Generic struct conform to protocol with associatedType got "Cannot convert return expression of type 'String' to return type 'Key'" Error


protocol Container {
    associatedtype ItemType
    func getItem() -> ItemType
    func getItems() -> [ItemType]
}

struct TypedBackpack<TypedItemType: StringProtocol>: Container {
    func getItem() -> TypedItemType {
        return ""
    }
    
    func getItems() -> [TypedItemType] {
        return [""]
    }
}

struct KeyBackpack<Key: Sequence>: Container {
    func getItem() -> Key { //"Error: Cannot convert return expression of type 'String' to return type 'Key'"
        return ""       
    }
    func getItems() -> [Key] { //"Error: Cannot convert return expression of type 'String' to return type 'Key'"
        return [""]
    }
}

I got "Error: Cannot convert return expression of type 'String' to return type 'Key'" for "getItem()" and "getItems()" functions in KeyBackPack. However, TypedBackPack is working fine.

If I change KeyBackPack to:

struct KeyBackpack<Key: StringProtocol>: Container {
    func getItem() -> Key {
        return ""       
    }
    func getItems() -> [Key] { 
        return [""]
    }
}

Everything works fine. So the problem is "<Key: Sequence>". Can anybody tell me why <Key: StringProtocol> works fine but <Key: Sequence> doesn't?


Solution

  • According to your declarations, I am able to use any Sequence as the type parameter of KeyBackpack, and getItem will return an instance of that type.

    So in theory, I could do this:

    let intArray = KeyBackpack<[Int]>().getItem()
    

    [Int] if obviously a Sequence, and getItem should give me an [Int]. However, what getItem actually returns, is an empty string! Clearly that does not make sense!

    Why does Key: StringProtocol work then?

    This is because StringProtocol inherits from ExpressibleByStringInterpolation, which in turn inherits from ExpressibleByStringLiteral.

    This means that you can implicitly convert a string literal to Key, because Key conforms to ExpressibleByStringLiteral.

    In more concrete terms, StringProtocol inherits the requirement init(stringLiteral:) from ExpressibleByStringLiteral, and the compiler automatically translates return "" to a call to that initialiser. You can think of this as:

    return Key(stringLiteral: "")
    

    This also means that if you return something other than a string literal, Key: StringProtocol would not have worked. e.g.

    let s = "" // here the type of "s" is inferred to be String 
    return s // "cannot convert type..." error because "s" is not a string literal, nor of type "Key"