Search code examples
swiftgenericsswift-protocolsassociated-types

Some Problems about Opaque Return Type and Protocol with associatedtype In Swift


How to deal with this problem?

Definitions:

protocol NameProtocol {
    var rawValue: String { get }
}
struct CatName: NameProtocol {
    enum CatNamesEnum: String {
        case Tiger, Max, Sam
    }
    var literal: CatNamesEnum
    var rawValue: String {
        literal.rawValue
    }
}
struct DogName: NameProtocol {
    enum DogNamesEnum: String {
        case Jack, Toby, Sadie
    }
    var literal: DogNamesEnum
    var rawValue: String {
        literal.rawValue
    }
}

Definitions:

protocol AnimalProtocol {
    associatedtype Name: NameProtocol
    var name: Name { get }
    func cry() -> String
}
class Cat: AnimalProtocol {
    var name: CatName
    func cry() -> String {
        return "meow, I am \(name.rawValue)"
    }
    init(name: CatName) {
        self.name = name
    }
    // some other cat actions...
}
class Dog: AnimalProtocol {
    var name: DogName
    func cry() -> String {
        return "bark, I am \(name.rawValue)"
    }
    init(name: DogName) {
        self.name = name
    }
    // some other dog actions...
}

The code above are some definition code structure, should not be modified.

And the functions below takes some problem:

  1. Protocol with asccociatedtype cannot be the dictionary value type.
  2. Function with Opaque Return Type cannot return some different types extends the same protocol.
// <1>
// Error: Protocol 'AnimalProtocol' can only be used as a generic constraint because it has Self or associated type requirements

let animals: [String: AnimalProtocol] = [
    "cat": Cat(name: CatName(literal: .Sam)),
    "dog": Dog(name: DogName(literal: .Jack))
        // ...
        // maybe a lot of animals
]
for (animal, entity) in animals {
    print("\(animal): \(entity.cry())")
}
// <2> 
// Error: Function declares an opaque return type, but the return statements in its body do not have matching underlying types
func animalCry(animal: String) -> some AnimalProtocol {
    switch animal {
    case "cat":
        return Cat(name: CatName(literal: .Sam)) 
    default:
        return Dog(name: DogName(literal: .Toby)) 
    }
}

Any solutions? Or best practice of different types(with embed generic type) in a list.


Solution

  • You say that the definitions should not be modified but this is exactly what I have done here:

    I removed the associated type from the AnimalProtocol protocol since it wasn't used elsewhere in any conforming types

    protocol AnimalProtocol {
        var name: NameProtocol { get }
        func cry() -> String
    }
    

    Then I changed both Cat and Dog to conform to the protocol by changing the name declaration in both to

    var name: NameProtocol
    

    this resolves the issue with the dictionary and the function was fixed by changing the return type from some AnimalProtocol to AnimalProtocol