swiftoption-typedowncastforced-unwrapping

Treating a forced downcast as optional will never produce 'nil'


I've been playing around with Swift and discovered that when down casting an object to be inserted into a dictionary, I get a weird warning: Treating a forced downcast to 'String' as optional will never produce 'nil'. If I replace as with as? then the warning goes away.

func test() -> AnyObject! {
  return "Hi!"
}

var dict = Dictionary<String,String>()
dict["test"]=test() as String

Apple's documentation says the following

Because downcasting can fail, the type cast operator comes in two different forms. The optional form, as?, returns an optional value of the type you are trying to downcast to. The forced form, as, attempts the downcast and force-unwraps the result as a single compound action.

I'm unclear as to why using as? instead of as is correct here. Some testing reveals that if I change test() to return an Int instead of a String, the code will quit with an error if I continue using as. If I switch to using as? then the code will continue execution normally and skip that statement (dict will remain empty). However, I'm not sure why this is preferable. In my opinion, I would rather the program quit with an error and let me know that the cast was unsuccessful then simply ignore the erroneous statement and keep executing.

According to the documentation, I should use the forced form "only when you are sure that the downcast will always succeed." In this case I am sure that the downcast will always succeed since I know test() can only return a String so I would assume this is a perfect situation for the forced form of down casting. So why is the compiler giving me a warning?


Solution

  • Let's take a closer look at your last line, and explode it to see what's happening:

    let temporaryAnyObject = test()
    let temporaryString = temporaryAnyObject as String
    dict["test"] = temporaryString
    

    The error is on the second line, where you are telling the compiler to enforce that temporaryAnyObject is definitely a String. The code will still compile (assuming you don't treat warnings as errors), but will crash if temporaryAnyObject is not actually a String.

    The way as works, with no ?, is basically saying "from now on, treat the result of this expression as that type IF the result is actually of that type, otherwise we've become inconsistent and can no longer run.

    The way as? works (with the ?) is saying "from now on, treat the result of this expression as that type IF the result is actually of that type, otherwise the result of this expression is nil.

    So in my exploded example above, if test() does return a String, then the as downcast succeeds, and temporaryString is now a String. If test() doesn't return a String, but say an Int or anything else not subclassed from String, then the as fails and the code can no longer continue to run.

    This is because, as the developer in complete control, you told the system to behave this way by not putting the optional ? indicator. The as command specifically means that you do not tolerate optional behavior and you require that downcast to work.

    If you had put the ?, then temporaryString would be nil, and the third line would simple remove the "test" key/value pair from the dictionary.

    This might seem strange, but that's only because this is the opposite default behavior of many languages, like Obj-C, which treat everything as optional by default, and rely on you to place your own checks and asserts.

    Edit - Swift 2 Update

    Since Swift 2, the forced, failable downcast operator as has been removed, and is replaced with as!, which is much Swiftier. The behavior is the same.