5

I have been coding in Swift for a while, and I think I had to put a ! on all my let field variables that were not defined immediately.

Now I notice today that this piece of code does not compile and I am really surprised? why is this?

class MyClass : Mapper {
    var a: Bool!

    required init?(_ map: Map) {
    }

    // Mappable
    func mapping(map: Map) {
        a   <- map["a"]
    }
}

let myClass = MyClass()

if myClass.a { // Compiler not happy
    //  Optional type 'Bool!' cannot be used as a boolean; test for '!= nil' instead
}

if true && myClass.a { // Compiler happy

}

if myClass.a && myClass.a { // Compiler happy

}

Apple Swift version 2.2

Edit
Some people point out why am I using a let for a variable that will never change. I mentioned it is for field variables but I shorten the example. When using ObjectMapper (http://github.com/Hearst-DD/ObjectMapper), all the fields are not defined immediately in the init. This is why they are all either optional? or required!

jsgoupil
  • 3,788
  • 3
  • 38
  • 53
  • 3
    In `if` and `switch` statements, implicitly unwrapped optionals aren't automatically force unwrapped (if you just use them on their own). This is so you can safely compare them with `nil`. See http://stackoverflow.com/questions/33670991/why-force-unwrapping-is-required-in-case-of-enum-and-switch – Hamish May 06 '16 at 17:01
  • 2
    Shortest workaround: `if a! {` – vacawama May 06 '16 at 17:06
  • 1
    Wow, this is highly inconsistent and it becomes scary for the code reviewers. They will ask why am I not using an `if let` statement to prevent a crash. If I use `if true && a`, I'll get fired. – jsgoupil May 06 '16 at 17:11
  • 1
    For safety, `if a ?? false {`, which is less weird than `if let a = a where a {`. – vacawama May 06 '16 at 17:17
  • 1
    ... and also less ugly than `if case true? = a {` :) – Martin R May 06 '16 at 17:25
  • ... or `if a.boolValue {` – dfrib May 06 '16 at 17:29
  • A `let` property should almost never be an implicitly-unwrapped optional. Change it to a plain `Bool`. – rob mayoff May 06 '16 at 17:33
  • @dfri, `if a.boolValue {` will crash if `a` is `nil`. As will `if a as Bool {`. – vacawama May 06 '16 at 17:37
  • @originaluser2 The main reason is the usage of the ObjectMapper https://github.com/Hearst-DD/ObjectMapper . I have to set some properties as ? and other as !. – jsgoupil May 06 '16 at 17:46
  • @vacawama Indeed; this is likewise true for your own shortest workaround `if a! {` (safety of these workarounds is another discussion, imo; as rob mayoff writes; why even use an implicit unwrapped optional for this case) – dfrib May 06 '16 at 17:53
  • @dfri, I took your ellipsis to mean it was tagging on to the safe suggestions comments. – vacawama May 06 '16 at 17:56
  • @vacawama Ah, I missed that the ellipsis naturally bind to your _"For safety ..."_, I skimmed the previous comments too quickly and just thought we were listing alternatives to avoid the compile time error, my bad! – dfrib May 06 '16 at 17:59
  • 1
    Perhaps you should update your question with a concrete use-case. Couldn't `a` be `nil` if the object is initialized from some JSON? – Martin R May 06 '16 at 18:02
  • @dfri, no problem. This has been a fun discussion all around. – vacawama May 06 '16 at 18:04
  • @MartinR Here, I updated the use case. Now the question is completely lost with the usage of a third party library where I was asking a simple Swift question why Swift has to be so strict about if structure. – jsgoupil May 06 '16 at 18:11

2 Answers2

3

You can declare let a: Bool without a ! and without declaring true or false right then. The compiler will complain if it can't guarantee the value gets set before you use it though.

This works.

let a: Bool

a = true

if a { // Compiler happy

}

This works as well, because a is guaranteed to be set.

let a: Bool

if thingOne < thingTwo {
    a = true
} else {
    a = false
}

if a { // Compiler happy

}

However, this would not work because a is not guaranteed to be set before you try to use it.

let a: Bool

if thingOne < thingTwo {
    a = true
}

if a { // Compiler NOT happy
    // "Constant 'a' used before being initialized"
}

Now, if you can't guarantee that your variable will be set by the time you do the check, then you should really be using an optional var in the first place.

var a: Bool?

if a == true { // Compiler happy

}
Mike Cole
  • 1,291
  • 11
  • 19
  • I appreciate the answer. The main reason I couldn't get rid of my ! is because I am using an ObjectMapper https://github.com/Hearst-DD/ObjectMapper and it is recommended to use the ! in this situation. – jsgoupil May 06 '16 at 17:41
  • Without more context, it's hard to say if using `!` is appropriate in your case. There definitely are some weird situations with JSON serialization in particular. `NSJSONSerialization`, for example, cannot handle optionals and instead requires the `NSNull` object. Depending on what you're doing there and how ObjectMapper works, it might be necessary. – Mike Cole May 06 '16 at 17:48
1

A bit of history...

In Swift 1.0, it was possible to check if an optional variable optVar contained a value by just checking:

if optVar {
    println("optVar has a value")
} else {
    println("optVar is nil")
}

In The Swift Programming Language, the update for Swift 1.1 (dated 2014-10-16) stated:

Optionals no longer implicitly evaluate to true when they have a value and false when they do not, to avoid confusion when working with optional Bool values. Instead, make an explicit check against nil with the == or != operators to find out if an optional contains a value.

So, the nonsensical error message that you are getting was put there because the Swift compiler is interpreting your:

if a {
}

to mean:

if a != nil {
}

and it is encouraging you to test against nil to determine if the Optional a has a value.

Perhaps the Swift authors will change it in the future, but for now you will have to explicitly unwrap a:

if a! {
}

or check against true:

if a == true {
}

or (to be completely safe):

if a ?? false {
    print("this will not crash if a is nil")
}
vacawama
  • 150,663
  • 30
  • 266
  • 294