0

I can see in the Apple documentation, when if let, the ? is used after as. But I tried this and the compile would not complain and the code behaved the same as as? NSError. So is this acceptable in Swift (not standard though), and if it is, is there any difference?

if let error = error as NSError? { ...

when it comes to

var arr = [Int8?]()        
arr.append(29 as Int8?)
arr.append(1 as? Int8) // warning: Conditional downcast from literal to 'Int8' always fails; consider using 'as' coercion

Why in this case the downcasting will always fails?

刘maxwell
  • 187
  • 10
  • 1
    When you put the ? after the type it means that the value is declare d to be optional (no cast is done), when you put it after `as` it means cast the value if possible otherwise return nil – Joakim Danielson May 14 '22 at 15:11
  • I think you're confusing `as`/`as?`/`as!` with C-style casting (e.g. `(char) someInt`). In Swift, `as` and its variants do not changes the type of the value at runtime (with some exceptions about Objective-C interop when Foundation is imported, but I'll ignore those). What these operators do is make runtime type-checks, which if successful, lead to the same value being referenced by a different static type. To actually convert the value from one integer type to another, you would use the respective initalizers, like `UInt8(someInt)`. – Alexander May 14 '22 at 16:36
  • I'm reopening this, because I don't think the linked question was correct. It was about using `as?` to unwrap optionals, where as this question actually involves no optional unwrapping at all. – Alexander May 14 '22 at 17:05

1 Answers1

1

For the purposes of most of this answer, I'll ignore the bridging features Swift has for inter-operating with Objective-C (when Foundation is imported).


re: arr.append(29 as Int8?)

as works just like a type annotation. No values are changed, you're just giving extra type information to the compiler, so arr.append(29 as Int8?) works as if you had written:

var arr = [Int8?]()
let i: Int8? = 29
arr.append(i)

However, the compiler already knows that arr is an [Int8?] (a.k.a. Array<Optional<Int8>>), whose Element type is Int8?. As a result, it already knows that the argument to append needs to be an Int8?. This, coupled with the fact that Swift can automatically promote a non-optional value (e.g. Int8) into an optional value (Int8?) when that's useful, you could just write:

arr.append(29)

re: arr.append(1 as? Int8)

This snippet needs a bit more explanation. You see, 1 is not an Int in Swift, although it can be.

It's an integer literal, which can be used to initialize a value of any type that conforms to ExpressibleByIntegerLiteral.

The fact that above you're able to write let i: Int8? = 29 instead of let i: Int8? = Int8(29) comes as a direct consequence; Int8 conforms to ExpressibleByIntegerLiteral (as does Float, Double, and every other signed and unsigned integer type).

The compiler will use contextual type information to pick what the best ExpressibleByIntegerLiteral-conforming type a given integer literal should be. This could come from several places:

  1. An explicit type annotation, like let i: Int8 = 123

  2. Using as, like let i = 123 as Int8

  3. Returning from a function with a known return type, such as:

    func returnsInt8() -> Int8 {
        return 123 // We know this must be Int8 from the return type
    }
    
  4. Passing as an argument to a parameter with a known type, such as:

    func takesAnInt8(_: Int8) {}
    takesAnInt8(123) // This argument can fit the parameter's type if it's Int8
    

In the absence of any of this type contextual information, the compiler will default to initializing literals into IntegerLiteralType, which is Int by default.

So when you write: 1 as? Int8, it's as if you wrote:

let i = 1 // (this is an `Int`)
arr.append(i as? Int8)

The problem becomes clear: i is statically typed to an Int, and there no scenario in which Int is an Int8, so this cast will always fail.

re: error as NSError?

This works because you have Foundation imported, which introduces some magical bridging that's intended to make interoperation with Objective C. For example, you can do:

let aSwiftString: Swift.String = "abc"
someObjCApi(aSwiftString as NSString) // Now it's a `Foundation.NSString`

Which will cause the runtime value to be bridged. As you saw, you can also bridge from Swift.Error to NSError (and from Swift.Error? to NSError?). This complicates the language a bit, because it means that the explanation of "as only does static type annotation with no runtime effect" is no longer true.

Alexander
  • 59,041
  • 12
  • 98
  • 151