Confused on 'if let a = b as NSError?'

Stumped on how to uinderstand why one form of a 'if let' works but another does not. Xcode 9.3


There is an Objective C file that declares a block type where the first parameter is an NSError. Without any nullability annotation, its viewed by Swift as an Optional.


typedef void(^MyError)(NSError *error, NSInteger type) ; // in ObjC header file


In Swift I see this is:


typealias MyError = (Error?, Int) -> Void


In the Swift file, in a MyError block:



if let err = error as NSError? {
  // works
}
if let err = error {
  // fails since err is of type Error - its not an NSError
}
if let err = error as? NSError {
  // fails with cryptic message on Error conversion
}


So let me take a stab at this. `let err = error as NSError?` translates as, if the optional Error is actually a optional NSError AND is non-nil, then set err to this NSError. Its type is now NSError. [I sure don't remember using if let x = y as z? in the past - the trailing "?".


I poked around the Swift Programming Guide for a while, was unable to find this construct.

Accepted Reply

Please do not say just `cryptic message` and show the full message you get.


Tested in Xcode 9.4.1:

import Foundation

typealias MyError = (Error?, Int) -> Void

let myErrorBlock: MyError = { error, num in
    if let err = error as NSError? {
        // works
        print(err)
    }
    if let err = error { // This line does not show any errors or warnings
        // fails since err is of type Error - its not an NSError
        print(err)
    }
    if let err = error as? NSError { //-> warning: conditional downcast from 'Error?' to 'NSError' is a bridging conversion; did you mean to use 'as'?
        // fails with cryptic message on Error conversion
        print(err)
    }
}

The second if-let does not fail, it fails when you try to use `err` as an `NSError`.


`let err = error as NSError?` translates as, if the optional Error is actually a optional NSError AND is non-nil, then set err to this NSError. Its type is now NSError.

It's a little bit different, in Swift `Error` to `NSError` conversion is an always-succeesds bridging, `Error?` to `NSError?` as well.


So, first error (of type `Error?`) is converted to `NSError?` successfully.

After that it is checked against nil and if the optional NSError is non-nil, then set err to this unwrapped `NSError`.


You may need to read this carefully if you want to know why `Error` to `NSError` has become an always-succeesds bridging.

SE-0112 Improved NSError Bridging (github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md)

(Frankly, I really do not understand if all the proposals in the article were really needed...)


In your third case, `Error?` to `NSError` is not an always-succeesds conversion, but it seems Swift does not like using `as?` when bridging is concerned.

Replies

On the 3rd, do you get an error or a warning ?

Which Swift version ?


I tested with Error, not NSError

With Swift4.2, I get a warning to replace 3rd by 1st, but not an error.

Please do not say just `cryptic message` and show the full message you get.


Tested in Xcode 9.4.1:

import Foundation

typealias MyError = (Error?, Int) -> Void

let myErrorBlock: MyError = { error, num in
    if let err = error as NSError? {
        // works
        print(err)
    }
    if let err = error { // This line does not show any errors or warnings
        // fails since err is of type Error - its not an NSError
        print(err)
    }
    if let err = error as? NSError { //-> warning: conditional downcast from 'Error?' to 'NSError' is a bridging conversion; did you mean to use 'as'?
        // fails with cryptic message on Error conversion
        print(err)
    }
}

The second if-let does not fail, it fails when you try to use `err` as an `NSError`.


`let err = error as NSError?` translates as, if the optional Error is actually a optional NSError AND is non-nil, then set err to this NSError. Its type is now NSError.

It's a little bit different, in Swift `Error` to `NSError` conversion is an always-succeesds bridging, `Error?` to `NSError?` as well.


So, first error (of type `Error?`) is converted to `NSError?` successfully.

After that it is checked against nil and if the optional NSError is non-nil, then set err to this unwrapped `NSError`.


You may need to read this carefully if you want to know why `Error` to `NSError` has become an always-succeesds bridging.

SE-0112 Improved NSError Bridging (github.com/apple/swift-evolution/blob/master/proposals/0112-nserror-bridging.md)

(Frankly, I really do not understand if all the proposals in the article were really needed...)


In your third case, `Error?` to `NSError` is not an always-succeesds conversion, but it seems Swift does not like using `as?` when bridging is concerned.