Any? incorect semantics - swift

I was playing around with some code in swift and encountered one interesting case.
Lets start with a little preamble: suppose you create some optional variables:
let a: String? = "abcd"; let b: Int? = 4
print(
"Type of \(a) is \(type(of: a))" //Type of Optional("abcd") is Optional<String>
"Type of \(b) is \(type(of: b))", //Type of Optional(4) is Optional<Int>
separator: "\n"
)
Then you force unwrap so types of au and bu are not optional.
let au = a!; let bu = b!
print(
"Type of \(au) is \(type(of: au))", //Type of abcd is String
"Type of \(bu) is \(type(of: bu))", //Type of 4 is Int
au + String(bu), //abcd4
separator: "\n"
)
Seem reasonable, but things start to go weird, when you try to apply same code to Optional<Any>:
let a: Any? = "abcd"; let b: Any? = 4
let au = a!; let bu = b!
print(
"Type of \(a) is \(type(of: a))", //Type of Optional("abcd") is Optional<Any>
"Type of \(b) is \(type(of: b))", //Type of Optional(4) is Optional<Any>
"Type of \(au) is \(type(of: au))", //Type of abcd is String
"Type of \(bu) is \(type(of: bu))", //Type of 4 is Int
//au + String(bu),
separator: "\n"
)
But now if you try to to do same concatenation au + String(bu), swift will produce compilation error, even though these two variables are known to be of some concrete type, as reported by swift itself.
The error is:
error: protocol type 'Any' cannot conform to 'LosslessStringConvertible' because only concrete types can conform to protocols
This certainly looks like a bug, isn't it. Please, share your opinion.

As others have noted, type(of:) is the dynamic, runtime type of a value. The compiler relies only on the static, provable type of a value. In the code above, the only thing that the compiler can prove is that au will be of type Any. One of the many types that conform to Any is String, but the compiler doesn't know that in all possible code paths, the value really will be a String.
Since there is no func + (Any, String) overload, this can't compile.
Note that Any? is a bizarre and dangerous type. The problems with it aren't the cause of this example, but the way that it interacts with Optional promotion can lead to significant ambiguity and confusion. Every type in Swift can be implicitly promoted to an Optional of that type. And every type in Swift can be implicitly cast to Any. Combining those two facts means that Any can be implicitly promoted to Any?, or Any??, or Any???, etc., and yet all of those are also subtypes of Any. This can create all kinds of subtle headaches. You should strongly avoid using Any; it is very seldom the right tool. But you should even more careful of allowing Any? to show up in your code. It is an indication that something has probably gone wrong.

type(of: T) method gets runtime type of any variable. That is why you are seeing (type(of: au) as String. But Swift will not allow implicit type casting for safety reasons. That is the reason you can not add Int and Double without casting in Swift. You need to cast explicitly for your code to work.

type(of: T) Returns the dynamic type of a value.
You can use the type(of:) function to find the dynamic type of a value,
particularly when the dynamic type is different from the static type. The
static type of a value is the known, compile-time type of the value. The
dynamic type of a value is the value's actual type at run-time, which
can be a subtype of its concrete type.
This explanation is taken from comment above type(of: T) function. Do Cmd+click on type(of: T) in Xcode to read more

Related

Where should I put my question mark in Swift?

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?
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:
An explicit type annotation, like let i: Int8 = 123
Using as, like let i = 123 as Int8
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
}
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.

Weird optional type behaviour in swift forEach

This code works fine. It iterates my array of one Int! and prints its magnitude.
import Foundation
let x : Int! = 1
[x].forEach {i in
print(i.magnitude)
}
Output:
1
Presumably, i in the loop body is an Int or an Int!, and indeed if I ask Xcode for "quick help" on forEach it reports:
func forEach(_ body: (Int) throws -> Void) rethrows
However, if perform two statements in my forEach body, instead it fails to compile, complaining that I need to unwrap i which now has the optional type Int?.
import Foundation
let x : Int! = 1
[x].forEach {i in
print(i.magnitude)
print(i.magnitude)
}
Compile error:
Value of optional type 'Int?' must be unwrapped to refer to member 'magnitude' of wrapped base type 'Int'
And if I ask for "quick help" now I get:
func forEach(_ body: (Int?) throws -> Void) rethrows
How on earth does the number of statements I place in my loop body manage to affect the type of the loop variable?
Basically, you've elicited an edge case of an edge case. You've combined two things that are the work of the devil:
Implicitly unwrapped Optionals
Implicit type inference of closures, along with the fact that
Implicit type inference of closures works differently when the closure consists of one line (this is where the "How on earth does the number of statements" comes in)
You should try to avoid both of those; your code will be cleaner and will compile a lot faster. Indeed, implicit type inference of anything other than a single literal, like a string, Int, or Double, is a huge drag on compilation times.
I won't pretend to imitate the compiler's reasoning; I'll just show you an actual solution (other than just not using an IUO in the first place):
[x].forEach {(i:Int) in
print(i.magnitude)
print(i.magnitude)
}
Our Int type is legal because we take advantage of the single "get out of jail free" card saying that an implicitly unwrapped Optional can be used directly where the unwrapped type itself is expected. And by explicitly stating the type, we clear up the compiler's doubts.
(I say "directly" because implicit unwrappedness of an Optional is not propagated thru passing and assignment. That is why in your second example you discovered Int?, not Int, being passed into the closure.)

Why is casting a struct to AnyObject not a compile error in swift?

The code below compiles and works in swift.
struct TestStruct {
let value: String = "asdf"
}
func iWantAReferenceType(object: AnyObject) {
print(String(describing: object))
}
let o: TestStruct = TestStruct()
iWantAReferenceType(object: o as AnyObject)
I expected this to be a compile error because a struct can never conform to AnyObject. As demonstrated below by code that fails to compile.
protocol Test: AnyObject {
}
//Compile error: because a struct cannot be AnyObject
struct TestStruct: Test {
let value: String = "asdf"
}
I am aware there is some bridging that can happen for certain types such as String. This would convert the value type of a reference type.
print(Mirror(reflecting: "asdf").subjectType) //print: String
print(Mirror(reflecting: "asdf" as AnyObject).subjectType) //print: NSTaggedPointerString
In writing this question I thought to see what the type of the cast object was and it seems it is also bridged in someway.
print(Mirror(reflecting: o).subjectType) //prints: TestStruct
print(Mirror(reflecting: o as AnyObject).subjectType) //prints: _SwiftValue
Why is this type of casting allowed? It seems to be breaking the contract for the function that is expecting a reference type.
I stumbled on this by accident when refactoring some code to support value types, to my surprise it had already been working for value types even though I thought it wouldn't. Is it safe to rely on this behaviour?
This is a feature to facilitate passing to Cocoa. Any struct can be wrapped into a SwiftValue reference type. If you print type(of: object) you'll see the wrapper.
I don't think there is any contract for "expecting a reference type." More importantly, while "value types" and "reference types" exist in Swift, what really matter is value and reference semantics, which are not expressible in the language. You can create value semantics in reference types and reference semantics in value types, so the Swift type system really isn't of any help in that regard.
The important point here is that you only get this unusual behavior if you explicitly request it by asking for as AnyObject. There are very few reason to write that, and if you are, you had better know exactly what you're doing.

what is the difference between creating type of the class and creating instance of the class [duplicate]

How are they different? I get a bit confused because they seem to be similar concepts.
How does understanding them help with optimizing compilation time?
From Swift's own documentation:
Type Safety
Swift is a type-safe language. A type safe language encourages you to be clear about the types of values your code can work with. If part of your code expects a String, you can’t pass it an Int by mistake.
var welcomeMessage: String
welcomeMessage = 22 // this would create an error because you
//already specified that it's going to be a String
Type Inference
If you don’t specify the type of value you need, Swift uses type inference to work out the appropriate type. Type inference enables a compiler to deduce the type of a particular expression automatically when it compiles your code, simply by examining the values you provide.
var meaningOfLife = 42 // meaningOfLife is inferred to be of type Int
meaningOfLife = 55 // it Works, because 55 is an Int
Type Safety & Type Inference together
var meaningOfLife = 42 // 'Type inference' happened here, we didn't specify that this an Int, the compiler itself found out.
meaningOfLife = 55 // it Works, because 55 is an Int
meaningOfLife = "SomeString" // Because of 'Type Safety' ability you will get an
//error message: 'cannot assign value of type 'String' to type 'Int''
Tricky example for protocols with associated types:
Imagine the following protocol
protocol Identifiable {
associatedtype ID
var id: ID { get set }
}
You would adopt it like this:
struct Person: Identifiable {
typealias ID = String
var id: String
}
However you can also adopt it like this:
struct Website: Identifiable {
var id: URL
}
You can remove the typealias. The compiler will still infer the type.
For more see Generics - Associated Types
Thanks to Swift’s type inference, you don’t actually need to declare a
concrete Item of Int as part of the definition of IntStack. Because
IntStack conforms to all of the requirements of the Container
protocol, Swift can infer the appropriate Item to use, simply by
looking at the type of the append(_:) method’s item parameter and the
return type of the subscript. Indeed, if you delete the typealias Item
= Int line from the code above, everything still works, because it’s clear what type should be used for Item.
Type-safety and Generics
Suppose you have the following code:
struct Helper<T: Numeric> {
func adder(_ num1: T, _ num2: T) -> T {
return num1 + num2
}
var num: T
}
T can be anything that's numeric e.g. Int, Double, Int64, etc.
However as soon as you type let h = Helper(num: 10) the compiler will assume that T is an Int. It won't accept Double, Int64, for its adder func anymore. It will only accept Int.
This is again because of type-inference and type-safety.
type-inference: because it has to infer that that the generic is of type Int.
type-safety: because once the T is set to be of type Int, it will no longer accept Int64, Double...
As you can see in the screenshot the signature is now changed to only accept a parameter of type Int
Pro tip to optimize compiler performance:
The less type inference your code has to do the faster it compiles. Hence it's recommended to avoid collection literals. And the longer a collection gets, the slower its type inference becomes...
not bad
let names = ["John", "Ali", "Jane", " Taika"]
good
let names : [String] = ["John", "Ali", "Jane", " Taika"]
For more see this answer.
Also see Why is Swift compile time so slow?
The solution helped his compilation time go down from 10/15 seconds to a single second.

What is the difference between Type Safety and Type Inference?

How are they different? I get a bit confused because they seem to be similar concepts.
How does understanding them help with optimizing compilation time?
From Swift's own documentation:
Type Safety
Swift is a type-safe language. A type safe language encourages you to be clear about the types of values your code can work with. If part of your code expects a String, you can’t pass it an Int by mistake.
var welcomeMessage: String
welcomeMessage = 22 // this would create an error because you
//already specified that it's going to be a String
Type Inference
If you don’t specify the type of value you need, Swift uses type inference to work out the appropriate type. Type inference enables a compiler to deduce the type of a particular expression automatically when it compiles your code, simply by examining the values you provide.
var meaningOfLife = 42 // meaningOfLife is inferred to be of type Int
meaningOfLife = 55 // it Works, because 55 is an Int
Type Safety & Type Inference together
var meaningOfLife = 42 // 'Type inference' happened here, we didn't specify that this an Int, the compiler itself found out.
meaningOfLife = 55 // it Works, because 55 is an Int
meaningOfLife = "SomeString" // Because of 'Type Safety' ability you will get an
//error message: 'cannot assign value of type 'String' to type 'Int''
Tricky example for protocols with associated types:
Imagine the following protocol
protocol Identifiable {
associatedtype ID
var id: ID { get set }
}
You would adopt it like this:
struct Person: Identifiable {
typealias ID = String
var id: String
}
However you can also adopt it like this:
struct Website: Identifiable {
var id: URL
}
You can remove the typealias. The compiler will still infer the type.
For more see Generics - Associated Types
Thanks to Swift’s type inference, you don’t actually need to declare a
concrete Item of Int as part of the definition of IntStack. Because
IntStack conforms to all of the requirements of the Container
protocol, Swift can infer the appropriate Item to use, simply by
looking at the type of the append(_:) method’s item parameter and the
return type of the subscript. Indeed, if you delete the typealias Item
= Int line from the code above, everything still works, because it’s clear what type should be used for Item.
Type-safety and Generics
Suppose you have the following code:
struct Helper<T: Numeric> {
func adder(_ num1: T, _ num2: T) -> T {
return num1 + num2
}
var num: T
}
T can be anything that's numeric e.g. Int, Double, Int64, etc.
However as soon as you type let h = Helper(num: 10) the compiler will assume that T is an Int. It won't accept Double, Int64, for its adder func anymore. It will only accept Int.
This is again because of type-inference and type-safety.
type-inference: because it has to infer that that the generic is of type Int.
type-safety: because once the T is set to be of type Int, it will no longer accept Int64, Double...
As you can see in the screenshot the signature is now changed to only accept a parameter of type Int
Pro tip to optimize compiler performance:
The less type inference your code has to do the faster it compiles. Hence it's recommended to avoid collection literals. And the longer a collection gets, the slower its type inference becomes...
not bad
let names = ["John", "Ali", "Jane", " Taika"]
good
let names : [String] = ["John", "Ali", "Jane", " Taika"]
For more see this answer.
Also see Why is Swift compile time so slow?
The solution helped his compilation time go down from 10/15 seconds to a single second.