3

I grasp (I think) the basics of optional types in Swift and roughy understand the difference between ? and !, but I'm still mystified by some of the results I get when I use these features — in particular the role of Some <T>, and how it differs from <T> itself; some of the specific error messages I get in certain cases; and how Some <T> seems to pop up in cases where I expect <T>.

But I also feel like even when I understand individual cases, my grasp of the picture gets away from me, and I feel like there is a code here that I could decipher if only I completely understood one simple example — a Rosetta Stone, if you will — for !, ?, optional values, and unpacking.

Here, for example, is a simple and (I think) exhaustive catalog of the basic cases:

class Foo {
    var one:String = "";
    var two:String?
    var three:String!
}

let test = Foo()        // {one "" nil nil}

test.one
//test.one?             //  ERROR: ? requires optional type
//test.one!             //  ERROR: ! requires optional type

// ?, unassigned
test.two                // nil
test.two?               // nil
//test.two!             // ERROR: EXEC_BAD_INSTRUCTION

test.two == nil         // true
test.two? == nil        // true
//test.two! == nil      // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

//test.two.isEmpty      // ERROR: String? does not have .isEmpty
test.two?.isEmpty       // nil
//test.two!.isEmpty     // ERROR: EXEC_BAD_INSTRUCTION

// !, unassigned
test.three              // nil
test.three?             // nil
//test.three!           // ERROR: EXEC_BAD_INSTRUCTION

test.three == nil       // true
test.three? == nil      // true
//test.three! == nil    // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

//test.three.isEmpty    // ERROR: EXEC_BAD_INSTRUCTION
test.three?.isEmpty     // nil
//test.three!.isEmpty   // ERROR: EXEC_BAD_INSTRUCTION


test.two = "???"        // {one "" {Some "???"} nil}
test.three = "!!!"      // {one "" {Some "???"} three "!!!"}

// ?, assigned
test.two                // {Some "???"}
test.two?               // {Some "???"}
test.two!               // "???"

test.two == nil         // false
test.two? == nil        // false
//test.two! == nil      // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

//test.two.isEmpty      // ERROR: String? does not have .isEmpty
test.two?.isEmpty       // {Some false}
test.two!.isEmpty       // false

// !, assigned
test.three              // "!!!"
test.three?             // {Some "!!!"}
test.three!             // "!!!"

test.three == nil       // false
test.three? == nil      // false
//test.three! == nil    // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

test.three.isEmpty      // false
test.three?.isEmpty     // {Some false}
test.three!.isEmpty     // false

If someone could annotate this, explaining what's going on it each case, I think that answer could serve as a solid reference for how these features of Swift work.

Community
  • 1
  • 1
orome
  • 45,163
  • 57
  • 202
  • 418
  • Note: I'm aware there are a lot of questions about this (and a lot great answers); I've read them all. What I'm looking for here is a bit different: an explanation of how the rules play out in *each* specific case, in one place, in the context of a single simple example. Also, this isn't a question about use; I think I get the reasons for the features. – orome Oct 02 '14 at 16:46

1 Answers1

4

Alright, I am going to try to answer this all. Might not have tie to get through everything:

NOTE: Feel free to call me out on errors. This took a while, so I surely made a few.

Quick note: Optional is actually an enum. It has two states: .None, and .Some(T), where T is the type of the value (in your case, String).

test.one

Foo has a property named one that returns an String. A definite String, not an optional, meaning that it will definitely have a value. You treat this similarly to how you would just write "HI!" in your code. The value of this is really ""

//test.one?             //  ERROR: ? requires optional type

This is an error because test.one, as said above, returns a definite String, and so there is no chance that it is nil. You can guarantee that the return value exists.

//test.one!             //  ERROR: ! requires optional type

Same as the ?. The ! is a forced unwrapping operator, meaning that there is a chance that test.one may be nil, but you want to force the value out anyway (or crash if it is not there). However, there is no chance it is nil, and so you cannot have a ? or a !.

test.two                // nil

test.two is a String?, which can be nil. Because it is optional, you are allowed to return nil like you do in your code. The real value of this is .None, and so the value you are seeing is actually a String? not a String.

test.two?               // nil

This basically does the same thing as the one above, except you are explicitly saying that the value possibly is nil.

//test.two!             // ERROR: EXEC_BAD_INSTRUCTION

You can never use a ! on a nil value without expecting it to crash. When you use this operator, it forces a value out of it (so you would have a String, not String?). However, if the value is nil, there is no value to force out, so you end up crashing the program.

test.two == nil         // true

test.two as is clear returns nil, or .None (they are equivalent). And so if you compare nil == nil or .None == .None, it is true.

test.two? == nil        // true

Same as the one above.

//test.two! == nil      // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

Force-unwrapping a nil value crashes the program, every time. It doesn't make sense, either, because force-unwrapping would return a String, not String?. String cannot be nil.

//test.two.isEmpty      // ERROR: String? does not have .isEmpty

Basically, whenever you want to call a method on an optional, you need to make sure it has a value using either optional-binding or optional-chaining (two separate things). String? is equal to Optional.Some(String), and you need to get past the optional layer to get to the string you want.

test.two?.isEmpty       // nil

Here you use optional-chaining. Basically, the way this works is that test.two is evaluated. If the value is .Some(String), then you call isEmpty on the string. Otherwise, if it is .None, then nothing happens. These optional chains can occur multiple lines per statement, such as test.two?.firstEmoji? (assuming such a method were implemented.

//test.two!.isEmpty     // ERROR: EXEC_BAD_INSTRUCTION

Again, force-unwrapping a nil optional is bad. Don't do it without first checking that the value is indeed .Some.

test.three              // nil

Since three is implicitly unwrapped, and it was initialized to nil (by not being set to something else), this shouldn't be surprising.

test.three?             // nil

This is not something you are likely to use in real code since it's essentially optional chaining, but without anything after it. However here, since .three is implicitly unwrapped ? has the effect of "re-wrapping" it: the type of the result is now String?. This make little difference here, but see what it does below, after test.three has had a String value assigned.

//test.three!           // ERROR: EXEC_BAD_INSTRUCTION

As above is not possible to unwrap nil. This may seem confusing since the declaration is often described as producing a variable that is "implicitly unwrapped"; but that should be read as "implicitly unwrapped if it is not nil".

test.three == nil       // true
test.three? == nil      // true
//test.three! == nil    // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

Same as 2 above. If you have a force-unwrapped variable, a ? appears to un-force-unwrap it, which is a behavior I would not advise. Try to use force-unwrapped optionals infrequently, mostly having to do with parts of the UI if you really need to. Often times, it will crash when you don't expect it to.

//test.three.isEmpty    // ERROR: EXEC_BAD_INSTRUCTION
test.three?.isEmpty     // nil
//test.three!.isEmpty   // ERROR: EXEC_BAD_INSTRUCTION

When an optional is not assigned, it defaults to nil. If you then try to force-unwrap it... I think you get the idea. The first and third lines try to call a method from String on nil (works in ObjC, not Swift). Second one uses optional chaining to check if it is nil before calling the method, which it can't because it know it is nil.

test.two = "???"        // {one "" {Some "???"} nil}
test.three = "!!!"      // {one "" {Some "???"} three "!!!"}

This sets test.two equal to .Some("???") and test.three equal to .Some("!!!") The output you see simply shows all the variables held in the class, and how they change.

test.two                // {Some "???"}
test.two?               // {Some "???"}
test.two!               // "???"

test.two is now .Some("???"), so when you call it, that is what is returned: a String? with a value. When you force-unwrap it, it now returns the value held in .Some without crashing because there is indeed a String in it.

test.two == nil         // false
test.two? == nil        // false

test.two is still an optional, so in the first two, when it compares them to nil, it realizes, "Hey, there is .Some value, so it is not nil."

//test.two! == nil      // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

Force-unwrapping a value turns the value of test.two from a String? to a String. Strings are never nil, because if they were, they would need to be optional. Comparing a value that is definitely a String to nil would make no sense, because you know for a fact that it is not nil; otherwise, the program would have crashed before!

//test.two.isEmpty      // ERROR: String? does not have .isEmpty

test.two is a String?, not a String. In order to access the string itself, you need to make sure it is there to access, using either a ? or a !

test.two?.isEmpty       // {Some false}

This says, "If test.two contains a String (not nil), then find if it is empty." It says {Some false} because you are accessing a member of an Optional still, not a direct String.

test.two!.isEmpty       // false

!, on the other hand, does return a String. Calling .isEmpty on a String is either true or false, which in this case is false because you set it equal to a non-empty string.

test.three              // "!!!"

test.three force-unwraps the String from it, which in this case works because it has a value.

test.three?             // {Some "!!!"}

You treat this as a normal optional (not force-unwrapped), and so you get a Some(String) instead of just a String.

test.three!             // "!!!"

Since you force-unwrapped it in its declaration, it is force-unwrapped here.

test.three == nil       // false

This is another strange behavior, as it should probably be an error. It is supposed to be a String, which cannot be compared to nil, but something wacky is going on here. I will get back to you when I find out about this.

test.three? == nil      // false

Treats test.three as a normal optional, and checks if its state is .None, or nil.

//test.three! == nil    // ERROR: Cannot invoke == with an argument list of type (@lvalue String, NilLiteralConvertable)

What the one two above should be like. Can't compare a String to nil, so it throws an error.

test.three.isEmpty      // false

Looks at the string value that was forced out (which exists; otherwise, it would have crashed). The string is not empty, so it is false.

test.three?.isEmpty     // {Some false}

Treats it as a String?. If test.three is not nil, then it takes the value from .Some (a String), and evaluates if it is empty, which it is not.

test.three!.isEmpty     // false

The String is forced out of the optional, and isEmpty is called directly to it. It is not empty, so it returns false.

I hope I helped clarify things, and I will let you know why that one case is the way it is when I find out for myself :]

orome
  • 45,163
  • 57
  • 202
  • 418
erdekhayser
  • 6,537
  • 2
  • 37
  • 69
  • Is it correct to say that `anOptional?` is a no-op? That is, that `anOptional?` is identical to `anOptional`, and that it's only if there is a following `.` or `[...]` that it has an effect? – orome Oct 04 '14 at 16:35
  • @raxacoricofallapatorius Can you point out the example you are talking about? I don't quite get what you are saying – erdekhayser Oct 04 '14 at 16:36
  • For example `test.three?` on its own. This will never produce anything different from `test.three`. – orome Oct 04 '14 at 16:40
  • @raxacoricofallapatorius after testing this line, when you put a ? after a force-unwrapped optional, it becomes a String? instead of staying a String!. I don't personally like how that works, because I think ! should only be used when you *absolutely* know that the value is non-nil. – erdekhayser Oct 04 '14 at 16:43
  • Ah, you're right. It's not a no-op at all. But the result does make sense: it could be `String` or it could be `nil` so: `String?`. – orome Oct 04 '14 at 16:50
  • The first case of `test.two! == nil` is a bit confusing to me. Instead of triggering an error on `test.two!` (as we do when `test.two!` is alone) we get an error on the `==` operator. What seems to be happening here is that `==` gets checked first, before checking what the actual value of `test.two` is (here `nil`) and based on the type it must be (`String`, even though it's `nil`!) an error occurs because there is no `==` that takes `String`, `nil` as arguments. Correct? – orome Oct 04 '14 at 19:03
  • I've got a [follow on](http://stackoverflow.com/q/26196710/656912) about one of the more vexing aspects (for me). – orome Oct 04 '14 at 20:09