Failable Initializers
Part of the Object Oriented Programming section of Coddy's Swift journey. Lesson 20 of 57.
Some values cannot be built from every input. A failable initializer, written init?, returns nil instead of an instance when the input is invalid, so no invalid instance can ever exist:
struct Percentage {
let value: Int
init?(_ value: Int) {
guard value >= 0, value <= 100 else { return nil }
self.value = value
}
}
print(Percentage(42)?.value ?? -1)
print(Percentage(150)?.value ?? -1)Output:
42
-1The result of a failable initializer is an optional, so the caller must unwrap it, usually with if let or guard let:
struct Email {
let address: String
init?(_ text: String) {
let parts = text.split(separator: "@")
guard parts.count == 2, parts[1].contains(".") else { return nil }
address = text.lowercased()
}
}
for text in ["Ada@Mail.com", "not-an-email"] {
if let email = Email(text) {
print("valid: \(email.address)")
} else {
print("invalid: \(text)")
}
}Output:
valid: ada@mail.com
invalid: not-an-emailSwift's own types use the same idea: Int("42") and Double("x") are failable initializers, and so is an enum's init(rawValue:). A failable initializer can call these and pass the failure on:
struct Coordinate {
let lat: Double
let lon: Double
init?(text: String) {
let parts = text.split(separator: ",")
guard parts.count == 2,
let lat = Double(parts[0]), let lon = Double(parts[1]),
abs(lat) <= 90, abs(lon) <= 180 else { return nil }
self.lat = lat
self.lon = lon
}
}
print(Coordinate(text: "51.5,-0.1") != nil)
print(Coordinate(text: "95,10") != nil)Output:
true
falseUse init? when a missing value is enough information. Use a throwing initializer, init(...) throws, when the caller should learn which rule failed:
struct Username {
let text: String
init(_ text: String) throws {
guard text.count >= 3 else { throw NameError.tooShort }
guard text.allSatisfy({ $0.isLetter }) else { throw NameError.badCharacters }
self.text = text
}
}Challenge
EasyComplete the struct Rgb, which stores a color as red, green and blue from 0 to 255. Its failable initializer init?(hex:) reads text like #FF8000: it returns nil unless the text is # followed by exactly six hexadecimal digits. Use Int(text, radix: 16) for each pair. Add a computed label that returns rgb(255, 128, 0).
The supplied code reads one color per line and prints the label or invalid: text.
Your code goes in Rgb.swift. main.swift holds the supplied input/output code and cannot be edited.
Try it yourself
// Supplied input/output code: keep it as it is
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
if let color = Rgb(hex: line) {
print(color.label)
} else {
print("invalid: \(line)")
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesMethods and selfInitializersClasses Are ReferencesRecap - Library Card4Encapsulation
Access ControlRead-Only From OutsideGuarding StateFailable InitializersRecap - Bank Account7Polymorphism and Generics
PolymorphismType CastingGeneric FunctionsGeneric TypesRecap - Shape Calculator5Inheritance
SubclassesOverriding MethodsInitializers and superfinal and HierarchiesRecap - Employee Hierarchy11Project: Library Management
Books and MembersBorrowing BooksPractice on your own: Swift playground