[Office Hours] is throwing in an initializer bad style, or does it depend? #93
-
Related DiscussionI'm curious if it is bad style for an initializer to throw an error, or if it depends? For example, let's say I was creating a struct
ReproductionN/A Additional contextNo response Code of Conduct
|
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Hi @kkellybonilla, It's not inherently bad practice for an initializer to throw an error. If you have a situation where an invalid argument would stop you from creating the object and/or crash your app, there is no reasonable default value, and you want the caller to understand exactly what happened and handle the failure explicitly, then throwing an error from the initializer makes sense. Here's how you might write that for your car example: struct Car {
let model: String
let currentGear: Int
init(model: String, currentGear: Int) throws {
guard (1...10) ~= currentGear else {
throw CarError.invalidGear(currentGear)
}
self.model = model
self.currentGear = currentGear
}
}
enum CarError: Error {
case invalidGear(Int)
} Here's how you might use it: do {
let myCar = try Car(model: "Camry", currentGear: 11) // Will throw an error
// use myCar
} catch CarError.invalidGear(let invalidGear) {
print("Invalid gear provided: \(invalidGear)")
// handle the error
} Note that we are able to identify the exact error that caused the problem and can handle it. The downside is that catching and handling the error can be cumbersome. If it's not important to tell the caller exactly why the initialization failed, we can also use a failable initializer. In this case, the initializer will return an optional, which will be struct Car {
let model: String
let currentGear: Int
init?(model: String, currentGear: Int) {
guard (1...10) ~= currentGear else {
return nil
}
self.model = model
self.currentGear = currentGear
}
} This is much simpler to use with optional binding, as shown below, but we cannot produce a precise error message indicating that the invalid gear was the issue. if let myCar = Car(model: "Camry", currentGear: 11) {
// use myCar
} else {
print("Failed to create car.")
} There is also a third option. You can write a throwing initializer as in the first example, but call it using if let myCar = try? Car(model: "Camry", currentGear: 11) {
// use myCar
} else {
print("Failed to create car.")
} In your case with the car example, it would probably be better to use a throwing initializer and properly handle the error, because an invalid gear is a serious issue for a car! Hope this helps! |
Beta Was this translation helpful? Give feedback.
Hi @kkellybonilla,
It's not inherently bad practice for an initializer to throw an error. If you have a situation where an invalid argument would stop you from creating the object and/or crash your app, there is no reasonable default value, and you want the caller to understand exactly what happened and handle the failure explicitly, then throwing an error from the initializer makes sense. Here's how you might write that for your car example: