Why Can't You Assign an Optional to a Variable of Type 'Any' Without a Warning

Why do we need to explicitly cast the optional to Any?

Every type can be implicitly promoted to an optional of that type. This means that when you cast T? to Any it is very hard to know whether it was originally T or originally T? (or even T?? or worse). Most confusing is that Any can be promoted to Any? and that Any? is of type Any, so telling the difference between Any, Any?, Any??, and Any??? (etc.) is very tricky, and sometimes impossible.

Any is a very tricky type in Swift and should almost never be used. Except for explicitly tricking the compiler (in some very fancy and fragile type-eraser), I don't know of any case where it really makes sense to have Any as a variable type, and definitely not in the form of [Any]. If you're created an [Any], you've gone down a bad path that isn't going to go well.

There are a very few cases where Any as a function parameter type makes sense (print() being the most famous), but they are extremely rare in app-level code. If you find yourself needing Any, you've probably done something wrong, and the compiler is going to fuss at you about it and often make you write extra as code to make sure you really mean the messy things you're saying.

Just to give some concrete versions of this, optionality tends to be lost when you enter Any. So consider this situation:

let number: Int = 3
let optionalNumber: Int? = 3
let nilNumber: Int? = nil

let anyNumber = number as Any
let anyOptional = optionalNumber as Any
let anyNil = nilNumber as Any

if anyNumber is Int { print("number is Int")} // yes
if anyOptional is Int { print("optional number is Int")} // yes
if anyNil is Int { print("nil is Int")} // no

if anyNil is Int? { print("nil is Int?")}
// -> Error: Cannot downcast from 'Any' to a more optional type 'Int?'

Rats.

We can't get our optional back the same way we put it in. We can promote it of course:

if (anyNil as Any?) is Int? { print("nil is Int?") }  // yes

But we can promote anything that way, since everything is implicitly an optional of itself:

if (anyNumber as Any?) is Int? { print("number is Int?")}  // also yes

So, um. Rats. We don't really know if it was originally optional or not. It's mess, and the compiler is warning you that it's going to be a mess if you go very far down this road. T->Any is a bit of magic. T->T? is also a bit of magic. Combine the two magics, and you had better know exactly what you're doing.

Why non optional Any can hold nil?

TL;DR; Optionals in swift are translated by the compiler to Optional enum instances, and since Any can map to any value, it can be used to store optionals.


How does Swift represent optionals? It does it by mapping SomeType? to a concrete implementation of the Optional enum:

Int? => Optional<Int>
String? => Optional<String>

A simplified declaration of Optional looks like this:

enum Optional<T> {
case none // nil
case some(T) // non-nil
}

Now, a variable of type Any is able to hold an enum value (or any other kind of value, or even metatype information), so it should be able to hold for example a nil String, aka String?.none, aka Optional<String>.none.

Let's see what happens, though. As we see by the Optional declaration, nil corresponds to the .none enum case for all types:

nil == Optional<String>.none // true
nil == Optional<Int>.none // true
[Double]?.none == nil // also true

So theoretically, you should be able to assign nil to a variable declared as Any. Still, the compiler doesn't allow this.

But why doesn't the compiler let you assign nil to an Any variable? It's because it can't infer to which type to map the .none enum case. Optional is a generic enum, thus it needs something to fill the T generic parameter, and plain nil is too broad. Which .none value should it use? The one from Int, the one from String, another one?

This gives an error message supporting the above paragraph:

let nilAny: Any = nil // error: nil cannot initialize specified type 'Any' (aka 'protocol<>')

The following code works, and is equivalent to assigning a nil:

let nilAny: Any = Optional<Int>.none

, as the above Any variable is actually holding a valid value of the Optional enum.

Indirect assignments work too, as behind the scenes nil is converted to Optional<Type>.none.

var nilableBool: Bool? // nilableBool has the Optional<Bool>.none value
var nilBoolAsAny: Any = nilableBool // the compiler has all the needed type information from nilableBool

Unlike other languages, in Swift nil corresponds to a concrete value. But it needs a type to work with, for the compiler to know which Optional<T>.none it should allocate. We can think of the keyword as providing sugar syntax.

Why if I typecast String? to Any, Xcode gives off warning, but not with AnyObject?

According to Swift Language Guide you are expected to get a warning when casting optional to Any (see note at the bottom of the page). You can get rid of warning by casting optional value to Any as shown below.

let email : String?;
let password : String?;
let dict = ["email": email as Any, "password": password as Any] as [String: Any];

Why should Java 8's Optional not be used in arguments

Oh, those coding styles are to be taken with a bit of salt.

  1. (+) Passing an Optional result to another method, without any semantic analysis; leaving that to the method, is quite alright.
  2. (-) Using Optional parameters causing conditional logic inside the methods is literally contra-productive.
  3. (-) Needing to pack an argument in an Optional, is suboptimal for the compiler, and does an unnecessary wrapping.
  4. (-) In comparison to nullable parameters Optional is more costly.
  5. (-) The risk of someone passing the Optional as null in actual parameters.

In general: Optional unifies two states, which have to be unraveled. Hence better suited for result than input, for the complexity of the data flow.

coerced to Any' but property is of type UIColor

This has nothing to do with .foregroundColor. It has everything to do with .tintColor and setTitleTextAttributes.

This parameter is of type [NSAttributedString.Key : Any]. It is not in any way considering the documentation for each key. It doesn't know or care that this should be a UIColor. If you passed "squid", this would compile without warning (it wouldn't work, but it would compile):

UIBarButtonItem.appearance().setTitleTextAttributes(
[
.font: UIFont.systemFont(ofSize: 40),
.foregroundColor: "squid",
], for: .normal)

All it's looking at is that you're assigning view.tintColor to a value of type Any.

The problem is that view.tintColor is not UIColor, it's UIColor!. It's not actually possible for .tintColor to be nil, but it's possible to set it to nil:

view.tintColor        // r 0.0 g 0.478 b 1.0 a 1.0
view.tintColor = .red
view.tintColor // r 1.0 g 0.0 b 0.0 a 1.0
view.tintColor = nil
view.tintColor // r 0.0 g 0.478 b 1.0 a 1.0

That makes sense in ObjC, but the only way to express it in Swift is to use a ! type. When you assign ! types to other things, they become ? types. And that means that you're using UIColor? in a place that accepts Any (the value of the dictionary).

Using an optional as Any can be dangerous because it creates a lot of weird corner cases. For example, you can't round-trip an optional through Any; it gets squashed into its base type:

let x: Any = Optional(1)
x as? Int? // Cannot downcast from 'Any' to a more optional type 'Int?'
x as? Int // 1

There are a lot of these kinds of little sharp-edges when working with Any.

Of course you didn't mean to work with Any. It's not your fault. But this is why Swift is complaining.

There are several solutions, depending on what you like. You can use a !:

    .foregroundColor: view.tintColor!

You can add as Any to silence the warning:

    .foregroundColor: view.tintColor as Any

Personally I'd use as Any.

Or you can be elaborate and unload the value earlier (I don't recommend this):

let tintColor = view.tintColor ?? .blue

UIBarButtonItem.appearance().setTitleTextAttributes(
[
.font: UIFont.systemFont(ofSize: 40),
.foregroundColor: tintColor,
], for: .normal)

Typescript variables with ? and?

When you write this:

class Optional {
id?: number;
}

you have declared that id is an optional property. That means it may or may not be present in an instance of Optional. If it is present, the value should be a number (or possibly undefined unless you are using the --exactOptionalPropertyTypes compiler option). If it is not present, then if you read the id property, the value will be undefined.

The above resolves the compiler warning because you are not required to initialize a property that's optional; it will just be undefined when you read it.

Anyway that means you cannot just use the id property as a number:

const o = new Optional();
o.id.toFixed(2) // compiler error!
//~~ <-- Object is possibly undefined

That warning is good, right? Because you don't want to accidentally read the toFixed() method of undefined. You need to check for undefined first, possibly by using the optional chaining operator (?.):

o.id?.toFixed(2) // okay

Optional properties are a reasonable approach when you are not sure if the properties will be assigned by the time users want to access them, and you want to protect these users from potential runtime errors.


On the other hand, when you write this:

class Asserted {
id!: number
}

you have asserted that the id property has definitely been assigned. That assertion is you telling the compiler something it can't verify for itself. This is useful in situations where you actually do assign the property but the compiler cannot follow the logic:

class ActuallyAssigned {
id!: number
constructor() {
Object.assign(this, { id: 123 });
}
}

An instance of ActuallyAssigned will have the id property set in the constructor, because the Object.assign() function copies properties into the first parameter from all subsequent parameters. But the compiler cannot understand this, and so without the definite assignment assertion, you'd get a warning. Silencing that warning is reasonable here.

But in Asserted above, it's not reasonable. You have lied to the compiler. It can't see that id has be assigned because it actually has not been assigned. And so you will not get a compiler warning if you go ahead and treat the id property like a number, even though it might be undefined:

a.id.toFixed(2) // no compiler error, but
// RUNTIME ERROR: o.id is undefined

Definite assignment assertions are therefore not recommended for situations when properties will quite possibly be undefined when users want to access them.

Playground link to code



Related Topics



Leave a reply



Submit