When Swift was announced back in 2014 I can remember thinking that I needed to make a pivot in my development approach and take all of my Objective-C knowledge and apply it towards developing in the Swift programming language going forward. At first, I thought Swift would be a one-for-one replacement of Objective-C and there would not be much of a difference in the development process other than the syntax. Soon, I realized that Swift was a much different language than Objective-C and there were many new language features that I had to wrap my head around, e.g., type inference, a newly enhanced memory management model, and a brand-new way to handle nil values called optionals. Over the next couple of years, as I started writing more and more Swift, I really started thinking about the purpose of optionals and how they enhance the language by promoting safety in type usage. And that is the basis for why I wanted to write this article, to discuss my thoughts on Swift optionals, provide a few examples on how optionals promote language safety over Objective-C’s usage of nil, and finally, discuss optional types in other languages such as C++ 17.
Swift Optionals
From a technical perspective, let’s start by defining a Swift optional as a type that handles a value being present or absent. In Swift, you cannot set a type to nil without that type being an optional and you cannot access the value in an optional type without unwrapping the optional first. The act of unwrapping an optional is a language feature that Swift provides to safely allow developers access to the data inside the type to avoid the misuse of nil and avoid a possible run-time crash. This is the part that I did not grasp back in 2014, but now, in the last couple of years, this is the part that has become the most compelling to me. Simply because I see less run-time crashes and a safer control flow of my data by using optionals in my program.
To better illustrate how Swift optionals work and the safety benefits gained from using them, suppose you have a firstName string optional that eventually will contain a value from a web service. Until the string becomes available in memory, a nil value will be carried around in the firstName optional representing the absence of a value. Once the string does become available, firstName can be set and the string optional can safely be unwrapped to obtain the actual string value. In Swift, this process that I described would look something like this:
var firstName: String? // … upon parsing firstName from web service firstName = “Matt Eaton” // Safely unwrapping the firstName string to gain access to Matt Eaton if let name = firstName { print(“Name: \(name)”) }
Safely unwrapping and using the firstName value in the context of the “if let” control flow enforces the safety of acting upon the value found in the name constant by ensuring that name will never be nil. Acting upon a nil value in Swift or Objective-C will cause a run-time crash. By safely unwrapping a value and using the name constant, you are guaranteeing that you can act upon the name value and nil crasher would not take place.
The example above uses the “if let” control flow to unwrap the value in the firstName optional and expose the name value inside the lexical context of the if statement. Let’s say however, there was a need to use the name value outside the scope of the if statement. In this case, you would use a guard statement to set the name value and expose the name value outside the context of the guard control statement. For example, such and implementation would look something like:
var firstName: String? // … upon parsing firstName from web service firstName = “Matt Eaton” guard let name = firstName else { return } // Use name outside of the control statement in which firstName was unwrapped print(name)
On last and very straight forward approach at unwrapping an optional to enforce safety is to use the simple ternary approach of safely unwrapping an optional or setting a default value if nil is present. For example, such and implementation would look something like:
firstNameLabe.text = firstName ?? “Not Available”
Objective-C Usage of nil
Now that I have covered what an optional is and how they are used in Swift, let’s talk about Objective-C’s usage of nil and where ultimately I believe the optional type in Swift came from. Objective-C allows nil values to be carried in a type just like Swift does, but Objective-C does not have optionals to enforce unwrapping and safely using a value the way that Swift does. In Objective-C you always run the risk of accidentally using a nil value unless you explicitly check for it first. As an example, consider the following code:
NSString *name = nil; NSLog(@"Name Before: %@", name); if ([@"Name in parent string" rangeOfString:name].location == NSNotFound) { NSLog(@"name is not found"); } else { NSLog(@"name if found"); } NSLog(@"Name After: %@", name);
The code above will ultimately crash if you run it, but notice that the first NSLog statement allows the string with a nil value to be printed. This is interesting because this does not enforce any sort of warning or safety mechanisms or unwrapping before nil is used incorrectly. If this same logic were written in Swift a case could be made that before the name value was used incorrectly that it would have to be unwrapped first and thus the if statement would never run and crash the program.
Knowing how Objective-C uses nil and how Swift forces a developer to safely work with nil has led me to formulate the opinion that cases like the one I described above were the basis for creation of Swift optionals in the first place. I think that over time Apple developers and external OSX and iOS developers simply grew tired over developing safe ways avoid acting upon nil in their code and so with this new language, Swift, optionals were born.
A Look at Optionals in C++ 17
Taking a step back now and taking a look at optionals from a much broader perspective in the context of C++ standard library, optionals are designed to also handle a value being present or absent. Straight from the C++ 17 reference it says, “Any instance of optional<T> at any given point in time either contains a value or does not contain a value.” Thinking about this documentation in C++ for a moment, this very well could also be a another valid origin of Swift Optionals, beings how some developers from Apple and the Swift core team contribute to C++ as well. In C++ 17, the usage of an optional looks a bit different than how the Swift type wraps up a value, but this is mainly due to syntax differences between the two languages. In the C++ 17 reference documentation for optionals, a code sample is given:
#include <string> #include <iostream> #include <optional> // optional can be used as the return type of a factory that may fail std::optional<std::string> create(bool b) { if (b) return "Godzilla"; return {}; } int main() { std::cout << "create(false) returned " << create(false).value_or("empty") << '\n'; // optional-returning factory functions are usable as conditions of while and if if (auto str = create(true)) { std::cout << "create(true) returned " << *str << '\n'; } }
With the output:
create(false) returned empty
create(true) returned Godzilla
This C++ 17 code sample is interesting because when a value is not present an optional is set to an empty value of the optional<t>, in this case an empty string. However, when an optional is empty in Swift, nil is carried around instead of an empty value of <t>. Just subtle difference, but working pointing out nonetheless.
In summary, I hope this article was insightful onto why optionals in the Swift ecosystem are so valuable and how they help enforce safety throughout the language. Swift optionals are a vast improvement to how Objective-C used nil values and I am sure happy that optionals were added from the ground up in Swift. Please let me know if you enjoyed the article or if you have any questions comments or concerns. Thank you for reading.
Comments
Objective C safety categories
Your example on rangeOfString is one of too many examples where the OS is not embracing nil. If you've been coding in ObjC for a decade or almost 2, you are probably well aware of these cases. In my case, many years ago I created a set of safety categories. This is one of them. [@"string" safeRangeOfString:nil] in my case returns {-1,0} as you would expect. :-)
That said, even my example fails in the case where you may do something like [(NSString *)nil safeRangeOfString:@"string"], which returns {0,0}.
I believe I could write dangerous Swift, just as I think I write safe ObjC.
I believe no language is a fix-all for bad practices, no matter how hard it tries.
thanks for the article!
Excellent idea, Eric Hayes…
Excellent idea, Eric Hayes and thank you for the feedback! That is a very clever way to safely avoid this type of pitfall in ObjC!