This post updated December 6, 2014 to reflect changes in Xcode 6.2
If you’ve been messing with Swift much lately, you may have noticed that enum’s do much more than they did in Objective-C, or in most other languages for that matter. In the WWDC videos discussing Swift, one of the presenters mentions that Optionals are implemented as enums. Let’s explore this a bit and see if we could possibly implement Optionals ourselves, as an exercise in exploring Swift’s enum type.
The first thing to note about an Apple’s implementation of Optional is that it’s an enum with two cases, Some and None. So first off, we create an enum, let’s call it JOptional, and add these cases.
enum JOptional { case None case Some }
When we set the enum case to ‘Some’, we should also have an associated value, but it can be any object type. So we use the T keyword to specify it’s a generic type.
enum JOptional<T> { case None case Some(T) }
We haven’t built out any fancy shortcuts that Swift’s regular optionals use yet, but we can still instantiate JOptionals directly if we add some init() methods.
enum JOptional<T> { case None case Some(T) init(_ value: T) { self = .Some(value) } init() { self = .None } } // Instantiate some optionals var myOptionalString = JOptional("A String!") var myNilString = JOptional()
Already we have a bit of a shortcut baked in here. The type of myOptionalString is JOptional<String>, but because we added a init(_ value: T) method to the enum, we can pass in:
- An unnamed parameter, due to the use of the _ for external name
- Of any type, since it is accepting a generic type of value T
- That initializes self, and sets the case to .Some with the attached value
Taking a look at the value of myOptionalString (I’m using a playground for this) you should see (Enum value). This is similar to how Swift’s optionals are “wrapped”. So, we need an unwrap method. Here is what I came up with:
func unwrap() -> Any { switch self { case .Some(let x): return x default: assert(true, "Unexpectedly found nil while unwrapping an JOptional value") } return JOptional.None }
This method uses the value-binding feature of Swift to determine if the enum value can be matched against .Some(let x):
If it can, then it’s safe to assume that x actually contains something, and since this is an unwrapping, we can return it.
Otherwise, this falls back to the default case, which is where we need to point out to the developer that they attempted to unwrap a JOptional that wasn’t able to be bound to x. I added an assert here, I believe this is also present in Apple’s Optional enum, since a force unwrapping of an Optional produces a hard crash.
We can test this out, like so:
var myOptionalString = JOptional("A String!") var a = myOptionalString.unwrap()
The value of a should now be to “A String!”, while myOptionalString should still be JOptional.Some(“A String!”), which will print as (Enum value).
In Apple’s Optional, one can unwrap by just appending ! to the end of the variable. We can’t use this symbol since it’s reserved, but there’s no reason we can add >! as a new postfix operator. Why >! you ask? I have no idea, but it’s not taken. Actually, this is a pretty good example of how to not choose operator overloads, but that’s okay. No one is ever going to use this, I hope.
On line 1 we create the postfix, then on line 2 we implement the overload to the operator
postfix operator >! {} postfix func >! <T>(value: JOptional<T> ) -> Any { return value.unwrap() }
Now, we can unwrap the optional with our custom operator
// Instantiate some optionals var myOptionalString = JOptional("A String!") var a = myOptionalString.unwrap() // "A String!" var c = myOptionalString>! // "A String!"
Full code for this part here »
This concludes part 1. In the next part we’ll implement nil comparison using the Equatable and NilLiteralConvertible interfaces.
Go To Part 2 »
This topic is also explored more in-depth in my upcoming book.
In the custom `>!` operator function, there is no need to declare the parameter as `inout`, as the operand does not been modified.
Yep, my bad. It was left there from an earlier iteration.
there’s a better way to implement JOptional::unwrap()
swift has a fatalError function, which does what you want to implement.
func unwrap() -> T {
switch self {
case .Some(let x): return x
case .None: fatalError(“Unexpectedly found nil while unwrapping an JOptional value”)
}
}
at the end you still get the type safety.
Hey, thanks for sharing this! I was actually struggling with the fact that I had to return something from the method, even though I knew I was just using an assert in the case of nil. It does seem to be the case however that the fatalError() is not documented, but it is good to see they have comments on it in the Swift lib:
/// A fatal error occurred and program execution should stop in debug mode. In
/// optimized builds this is a noop.
@noreturn func fatalError(message: StaticString, file: StaticString = default, line: UWord = default)
if you don’t want to use undocumented method you do this:
func unwrap() -> T {
switch self {
case .Some(let x): return x
case .None:
assert(true, “Unexpectedly found nil while unwrapping an JOptional value”)
return Optional.None as T
}
}
thanks the type-system 🙂
Nice! Thanks for this. I’ll be doing a follow up including this.