MiddleCodeCommonNot answered yet
Write a failable init? and a throwing init for one parsing type — when is each the right choice?
Give RGBColor, which parses a "#RRGGBB" hex string, two initializers on the same type.
Requirements:
init?(hex:)returnsnilfor any malformed input, reporting no reason.init(validating:)throws aParseErrornaming what was wrong (bad length, non-hex digit).- Both parse the same
"#RRGGBB"format.
struct RGBColor {
let r, g, b: UInt8
init?(hex: String) { /* your code here */ }
init(validating hex: String) throws { /* your code here */ }
}
Write both initializers.
Use init? when failure has one uninteresting cause and nil says enough; the caller just checks for nil. Use a throwing init when the caller needs the reason. init? discards the cause; throws carries a rich, propagating error.
- ✗Thinking
init?can hand back the failure reason the waythrowsdoes - ✗Believing a throwing
initreturnsnilrather than propagating an error - ✗Assuming failable initializers are class-only and unusable on value types
- →Which of the two composes better with
try?at the call site? - →When does returning
nillose information the caller genuinely needs?
One type, two failure contracts — silent and talkative:
struct RGBColor {
let r, g, b: UInt8
init?(hex: String) {
guard let rgb = RGBColor.parse(hex) else { return nil }
(r, g, b) = rgb
}
init(validating hex: String) throws {
guard hex.hasPrefix("#"), hex.count == 7 else {
throw ParseError.badLength(hex.count)
}
guard let rgb = RGBColor.parse(hex) else {
throw ParseError.nonHexDigit
}
(r, g, b) = rgb
}
}
enum ParseError: Error { case badLength(Int), nonHexDigit }
A caller uses init? as RGBColor(hex: s) ?? .black — no reason needed. init(validating:) gives a catch site that can show which field broke and localize the message. A failable initializer is legal on a struct and an enum, not just a class. The choice is whether the caller needs the reason, not which form is "more modern".