Skip to content

Instantly share code, notes, and snippets.

@DandyLyons
Created October 8, 2024 19:43
Show Gist options
  • Select an option

  • Save DandyLyons/ab101ecaa4d8a73c4202ed2cc8a0a12d to your computer and use it in GitHub Desktop.

Select an option

Save DandyLyons/ab101ecaa4d8a73c4202ed2cc8a0a12d to your computer and use it in GitHub Desktop.
Typed throws in Swift (multiple types)
import Foundation
struct IceCreamShop {
enum Error: Swift.Error {
case notEnoughMoney
case flavorNotSoldHere
case flavorError(IceCreamFlavor.Error)
}
private(set) var availableFlavors: [String: IceCreamFlavor]
private(set) var cashOnHand: Int
private(set) var isFreezerOn = true
private(set) var billLastPaidOn: Date
mutating func sellIceCream(flavorName: String) throws(Self.Error) {
guard isFlavorSoldHere(flavorName) else { throw .flavorNotSoldHere }
do throws(IceCreamFlavor.Error) {
try availableFlavors[flavorName]?.scoop()
} catch let error {
throw Self.Error.flavorError(error)
}
cashOnHand += 1
try payBillIfNecessary()
}
mutating func restockIceCream(flavorName: String, amount: Int) throws(Self.Error) {
guard cashOnHand > amount else { throw .notEnoughMoney }
guard isFlavorSoldHere(flavorName) else { throw .flavorNotSoldHere }
availableFlavors[flavorName]?.refillIceCream(amount: amount)
try payBillIfNecessary()
}
func isFlavorSoldHere(_ flavorName: String) -> Bool {
availableFlavors.keys.contains(flavorName)
}
mutating func payBillIfNecessary() throws(Self.Error) {
// pay bills if it has been more than a month since you last paid bills
if Calendar.current.dateComponents([.month], from: billLastPaidOn, to: Date()).month! > 1 {
try payBills()
}
}
mutating func payBills() throws(Self.Error) {
guard cashOnHand > 500 else {
isFreezerOn = false
throw .notEnoughMoney
}
billLastPaidOn = Date()
isFreezerOn = true
cashOnHand -= 500
}
}
struct IceCreamFlavor: Hashable {
enum Error: Swift.Error {
case flavorOutOfStock
case iceCreamMelted
}
private(set) var scoopsLeft: Int
var isMelted = false
mutating func scoop() throws(Self.Error) {
guard !isMelted else { throw .iceCreamMelted }
guard scoopsLeft > 0 else { throw .flavorOutOfStock }
scoopsLeft -= 1
}
mutating func refillIceCream(amount: Int) {
scoopsLeft += amount
}
}
/// an example of what the call site could look like
func timeline() {
var iceCreamShop = IceCreamShop(
availableFlavors: [
"Strawberry": IceCreamFlavor(scoopsLeft: 200),
"Chocolate": IceCreamFlavor(scoopsLeft: 1)
],
cashOnHand: 20,
billLastPaidOn: Date(timeIntervalSince1970: 0)
)
// ๐Ÿ‘‡๐Ÿผ This exhaustively handles every possible error, even across multiple types.
do throws(IceCreamShop.Error) {
try iceCreamShop.sellIceCream(flavorName: "Strawberry") // throws IceCreamShop.Error
} catch {
switch error {
case .flavorNotSoldHere:
print("Sir, this is a Baskin Robbins.")
case let .flavorError(flavorError):
switch flavorError {
case .flavorOutOfStock:
print("Shucks! We're out of that flavor.")
case .iceCreamMelted:
print("...would you like a milk drink instead? ๐Ÿ˜…")
}
case .notEnoughMoney:
print("Time for us to go out of business...")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment