Search code examples
swiftgenericsprotocolstype-erasure

How can I erase generics from a type in Swift?


I need to declare an array like this:

var cups: [Cup<Drink>] = []

The Cup is a struct and the Drink is a protocol, but I got the following error:

Value of protocol type 'Drink' cannot conform to 'Drink'; only struct/enum/class types can conform to protocols

I know the protocol type 'Drink' can be erased by a AnyDrink struct, the flowing code is an example.

But in fact, the Type-Erasure will become extremely complicated when associatetype, Self and static-method (in the case of non-final class adopts Drink, such as the protocol Equatable with static method ==) are used in Drink.

Is there a better way to declare the cups array?

Or: Is there any easy way to make Type-Erasure? It should be a builtin feature.

protocol Drink {
    ...
}

struct AnyDrink: Drink {
    let drink: Drink
    ...
}

struct Water: Drink {
    ...
}
struct Coffee: Drink {
    ...
}
struct Tea: Drink {
    ...
}

struct Cup<T: Drink> {
    private(set) var drink: T?
    mutating func bottomUp() {
        drink = nil
    }
}

struct Waiter {
    var cups: [Cup<AnyDrink>] = []
    mutating func makeACupOfSth(_ cup: Cup<AnyDrink>) {
        cups.append(cup)
    }
    mutating func pleaseGiveMeACupOfSthToDrink() -> Cup<AnyDrink> {
        return cups.removeFirst()
    }
    static func excuse(_ customer: Customer) -> Waiter {
        return Waiter()
    }
}

struct Customer {
    var me: Self { self }
    func drink() {
        var waiter = Waiter.excuse(me)
        var cup = waiter.pleaseGiveMeACupOfSthToDrink()
        cup.bottomUp()
    }
}

Solution

  • For question Is there a better way to declare the cups array?:

    You can use another protocol called DrinkGeneric like this and implement it by Cup Struct:

    protocol DrinkGeneric {
        func sample()
        func typOfDrink() -> Drink.Type
    }
    
    struct Cup<T: Drink>: DrinkGeneric {
        public var drink: T?
        mutating func bottomUp() {
            drink = nil
        }
    
        public func typeOfDrink() -> Drink.Type {
            return type(of: drink!)
        }
    
        func sample() {
            print("sample")
        }
    
    }
    

    Then create an array with type DrinkGeneric like this:

    var cups: [DrinkGeneric] = [Cup(drink: Water()), Cup(drink: Tea())]
    

    For check type:

    if cups[0].typeOfDrink() is Water.Type {
        // Any work
    }