Skip to content

Instantly share code, notes, and snippets.

@LeeKahSeng
Last active August 2, 2022 15:00
Show Gist options
  • Save LeeKahSeng/a347da1778c71f55537eda991bd56c64 to your computer and use it in GitHub Desktop.
Save LeeKahSeng/a347da1778c71f55537eda991bd56c64 to your computer and use it in GitHub Desktop.
Full sample code for article "How to Achieve Dynamic Dispatch Using Generic Protocols in Swift 5.7" (https://swiftsenpai.com/uncategorized/dynamic-dispatch-with-generic-protocols)
protocol Vehicle {
associatedtype FuelType: Fuel
var name: String { get }
func startEngin()
func fillGasTank(with fuel: FuelType)
}
struct Car: Vehicle {
let name: String
func startEngin() {
print("\(name) enjin started!")
}
func fillGasTank(with fuel: Gasoline) {
print("Fill \(name) with \(fuel.name).")
}
}
struct Bus: Vehicle {
let name: String
func startEngin() {
print("\(name) enjin started!")
}
func fillGasTank(with fuel: Diesel) {
print("Fill \(name) with \(fuel.name).")
}
}
protocol Fuel {
associatedtype FuelType where FuelType == Self
static func purchase() -> FuelType
}
struct Gasoline: Fuel {
let name = "gasoline"
static func purchase() -> Gasoline {
print("Purchase gasoline from gas station.")
return Gasoline()
}
}
struct Diesel: Fuel {
let name = "diesel"
static func purchase() -> Diesel {
print("Purchase diesel from gas station.")
return Diesel()
}
}
// MARK: - Functions
func startAllEngin(for vehicles: [any Vehicle]) {
for vehicle in vehicles {
vehicle.startEngin()
}
}
func fillAllGasTank(for vehicles: [any Vehicle]) {
for vehicle in vehicles {
fillGasTank(for: vehicle)
}
}
func fillGasTank(for vehicle: some Vehicle) {
let fuel = type(of: vehicle).FuelType.purchase()
vehicle.fillGasTank(with: fuel)
}
// MARK: - Execution
let vehicles: [any Vehicle] = [
Car(name: "Car_1"),
Car(name: "Car_2"),
Bus(name: "Bus_1"),
Car(name: "Car_3"),
]
startAllEngin(for: vehicles)
fillAllGasTank(for: vehicles)
@LeeKahSeng
Copy link
Author

Hello, you should review your code:

expression failed to parse:
error: PROTOCOL.playground:74:9: error: type 'any Vehicle' cannot conform to 'Vehicle'
        fillGasTank(for: vehicle)
        ^

PROTOCOL.playground:74:9: note: only concrete types such as structs, enums and classes can conform to protocols
        fillGasTank(for: vehicle)
        ^

PROTOCOL.playground:78:6: note: required by global function 'fillGasTank(for:)' where 'some Vehicle' = 'any Vehicle'
func fillGasTank(for vehicle: some Vehicle) {
     ^

Hi, I suspect the errors that you get are due to the wrong Swift version. Make sure to run the sample code using Swift 5.7 on Xcode 14 (currently in beta).

@gmcusaro
Copy link

It's not latest beta released this week but, the article is dated July 5. I'm pretty sure I'm doing everything right.

error

@LeeKahSeng
Copy link
Author

LeeKahSeng commented Jul 31, 2022

The sample code was originally written using beta 2 and I just tested it using beta 4 playground, everything seems to work fine. Beta 3 seems to have quite some problems, maybe you can try updating to beta 4 and test it again.

@gmcusaro
Copy link

gmcusaro commented Aug 2, 2022

I confirm that with beta 4, it works!

@CyberMew
Copy link

CyberMew commented Aug 2, 2022

Came across your blog a short while ago and I like the style! Great article, easy to understand with examples. Quite new to generics (and maybe Swift) so thanks a lot for sharing! I wanted something like this last year but couldn't figure out how, and I read online about type erasure to make it work (AnyMyBaseClassObject). Do you have an article on type erasure?

As for this, is it possible to do something like as some Vehicle or some kind of explicit cast without creating a new function? And what about changing any to some for func fillAllGasTank(for vehicles: [any Vehicle]), will that work?

@LeeKahSeng
Copy link
Author

As of now (Swift 5.7), there is only 1 way to convert opaque type to existential type and vice versa, which is by using a function parameter. Thus using as will not work. Furthermore, in order for the conversion to work, the function parameter must be of type any or some, using [any] or [some] will not work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment