Reputation: 6581
I have this code in Swift:
var dictionary: [String: Any?]?
fileprivate func value(forKey key: Key) -> Any? {
if let dictionary = self.dictionary {
return dictionary[key.rawValue]
}
...
}
I get a warning "Expression implicitly coerced from 'Any??' to 'Any?' in the return statement ". What am I doing wrong?
Upvotes: 2
Views: 4144
Reputation: 63369
Nobody seemed to actually explain the error message, so I'll go ahead and do that.
Suppose you had an integer, i
, which I'll represent it as this diagram:
let i = 123
// ┏━━━━━━━━━━━━┓
// ┃ Name: i ┃
// ┃ Type: Int ┃
// ┠────────────┨
// ┃ Value: 123 ┃
// ┗━━━━━━━━━━━━┛
Any
You can "box up" i
into a value of type Any
. This container "hides" the Int
ness of i
.
"s"
or true
.Any
. Because Any
is so broad, it doesn't really describe much that you can do with the value. You can't .lowercased()
it like a String
, you can't toggle()
it, like a Bool
, all you can really do is pass it around, and eventually down-cast it into another type which can do more things.When you box up i
into an Any
, it looks something like this:
let any123 = i as Any
// ┏━━━━━━━━━━━━━━━━━━┓
// ┃ Name: any123 ┃
// ┃ Type: Any ┃
// ┠──────────────────┨
// ┃ Value: ┃
// ┃ ┏━━━━━━━━━━━━┓ ┃
// ┃ ┃ Name: i ┃ ┃
// ┃ ┃ Type: Int ┃ ┃
// ┃ ┠────────────┨ ┃
// ┃ ┃ Value: 123 ┃ ┃
// ┃ ┗━━━━━━━━━━━━┛ ┃
// ┗━━━━━━━━━━━━━━━━━━┛
Any
is "opaque" to the type system, as in, the type system cannot determine what lies beyond the Any
box border (which is why I represent it with a solid outline).
Optional<T>
Optional is a generic enum with two cases, some
(which stores an associated value called wrapped
) and none
(which doesn't). It's generic over any type T
, but for the purposes of this example, I'll talk only about the case where T
is Int
, so Optional<Int>
, a.k.a. Int?
.
nil
is a shorthand for saying Optional<T>.none
, for any type T
.
let some123: Int? = 123 // implicitly wraps the value into an optional
let none: Int? = nil
// ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
// ╏ Name: some123 ╏ ╏ Name: none ╏
// ╏ Type: Int ╏ ╏ Type: Int? ╏
// ┠──────────────────┨ ┠───────────────┨
// ╏ Case: .some ╏ ╏ Case: .none ╏
// ╏ wrapped: ╏ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
// ╏ ┏━━━━━━━━━━━━┓ ╏
// ╏ ┃ Name: i ┃ ╏
// ╏ ┃ Type: Int ┃ ╏
// ╏ ┠────────────┨ ╏
// ╏ ┃ Value: 123 ┃ ╏
// ╏ ┗━━━━━━━━━━━━┛ ╏
// ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
Unlike Any
, optional is "transparent" to the type system (which is why I represent it with a dashed outline). You can always see the type of the value within.
Optionals can compose with each other, acting like Russian nesting dolls:
let someSome123: Int?? = 123 // Shorthand for Optional<Optional<Int>>.some(Optional<Int>.some(123))
let someOfNone: Int?? = .some(nil) // Shorthand for Optional<Optional<Int>>.some(Optional<Int>.none)
let none: Int?? = nil // Shorthand for Optional<Optional<Int>>.none
// ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍┓
// ╏ Name: someSome123 ╏ ╏ Name: someOfNone ╏ ╏ Name: none ╏
// ╏ Type: Int?? ╏ ╏ Type: Int?? ╏ ╏ Type: Int?? ╏
// ┠────────────────────────┨ ┠───────────────────┨ ┠─────────────┨
// ╏ Case: .some ╏ ╏ Case: .some ╏ ╏ Case: .none ╏
// ╏ wrapped: ╏ ╏ wrapped: ╏ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍┛
// ╏ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ ╏ ╏ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍┓ ╏
// ╏ ╏ Type: Int? ╏ ╏ ╏ ╏ Type: Int? ╏ ╏
// ╏ ┠──────────────────┨ ╏ ╏ ┠─────────────┨ ╏
// ╏ ╏ Case: .some ╏ ╏ ╏ ╏ Case: .none ╏ ╏
// ╏ ╏ wrapped: ╏ ╏ ╏ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍┛ ╏
// ╏ ╏ ┏━━━━━━━━━━━━┓ ╏ ╏ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
// ╏ ╏ ┃ Type: Int ┃ ╏ ╏
// ╏ ╏ ┠────────────┨ ╏ ╏
// ╏ ╏ ┃ Value: 123 ┃ ╏ ╏
// ╏ ╏ ┗━━━━━━━━━━━━┛ ╏ ╏
// ╏ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛ ╏
// ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
This happens for example, when you try to subscript a Dictionary that stores T?
values. There are 3 possible outcomes:
nil
. This is like the constant someSome123
above nil
. This is like the constant someOfNone
abovenone
aboveMany languages allow all types to store a null
value. This is a weaker design, because it can't distinguish between #2 and #3. "Does this null
mean the value is absent, or that a value is there, but the value itself is null
?" The nestability of optionals allows for that distinction . There are many other contexts in which it can come up, like accessing the first
value of an Array<Optional<T>>
.
Any
and Optional
Just like how optionals can contain optionals, optionals can contain values of type Any
, and vice versa. Here are some example that recreates your error:
let any123: Any = 123
let optionalOptionalAny123: Any?? = any123
let optionalAny123: Any? = optionalOptionalAny // ⚠️ warning: expression implicitly coerced from 'Any??' to 'Any?'
// ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓
// ╏ Name: optionalOptionalAny123 ╏ ╏ Name: optionalAny123 ╏
// ╏ Type: Optional<Optional<Any>> ╏ ╏ Type: Optional<Any> ╏
// ┠───────────────────────────────┨ ┠──────────────────────────────────┨
// ╏ Case: .some ╏ ╏ Case: .some ╏
// ╏ wrapped: ╏ ╏ wrapped: ╏
// ╏ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ ╏ ╏ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ╏
// ╏ ╏ Type: Optional<Any> ╏ ╏ ╏ ┃ Type: Any ┃ ╏
// ╏ ┠───────────────────────┨ ╏ ╏ ┠────────────────────────────┨ ╏
// ╏ ╏ Case: .some ╏ ╏ ╏ ┃ Value: ┃ ╏
// ╏ ╏ wrapped: ╏ ╏ ╏ ┃ ┏╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┓ ┃ ╏
// ╏ ╏ ┏━━━━━━━━━━━━━━━━━┓ ╏ ╏ ╏ ┃ ╏ Name: some123 ╏ ┃ ╏
// ╏ ╏ ┃ Name: any123 ┃ ╏ ╏ ╏ ┃ ╏ Type: Optional<Int> ╏ ┃ ╏
// ╏ ╏ ┃ Type: Any ┃ ╏ ╏ ╏ ┃ ┠─────────────────────┨ ┃ ╏
// ╏ ╏ ┠─────────────────┨ ╏ ╏ ╏ ┃ ╏ Case: .some ╏ ┃ ╏
// ╏ ╏ ┃ Value: ┃ ╏ ╏ ╏ ┃ ╏ wrapped: ╏ ┃ ╏
// ╏ ╏ ┃ ┏━━━━━━━━━━━┓ ┃ ╏ ╏ ╏ ┃ ╏ ┏━━━━━━━━━━━┓ ╏ ┃ ╏
// ╏ ╏ ┃ ┃ i: Int ┃ ┃ ╏ ╏ ╏ ┃ ╏ ┃ i: Int ┃ ╏ ┃ ╏
// ╏ ╏ ┃ ┠───────────┨ ┃ ╏ ╏ ╏ ┃ ╏ ┠───────────┨ ╏ ┃ ╏
// ╏ ╏ ┃ ┃ Value: 123┃ ┃ ╏ ╏ ╏ ┃ ╏ ┃ Value: 123┃ ╏ ┃ ╏
// ╏ ╏ ┃ ┗━━━━━━━━━━━┛ ┃ ╏ ╏ ╏ ┃ ╏ ┗━━━━━━━━━━━┛ ╏ ┃ ╏
// ╏ ╏ ┗━━━━━━━━━━━━━━━━━┛ ╏ ╏ ╏ ┃ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛ ┃ ╏
// ╏ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛ ╏ ╏ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ╏
// ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛ ┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛
As you can see, both optionalOptionalAny123
and optionalAny123
contain two layers of optionality, and one layer of type erasure (as provided by Any
). This warning exists to tell you that by coersing Any??
to Any?
, you're obscuring the second layer of optionality behind the hidden veil of Any
.
To get around this warning, you can follow one of its three suggestions:
provide a default value to avoid this warning
Peel back one layer of optionality by unwrapping its contents, or resorting to the default value if it's nil
. This is rarely a good idea, because sensible default values rarely exist.
let optionalAny123: Any? = optionalOptionalAny ?? 0
force-unwrap the value to avoid this warning
Peel back one layer of optionality by forcefully unwrapping it. This is rarely a good idea, because it requires you be absolutely certain you don't have a nil
.
let optionalAny123: Any? = optionalOptionalAny!
explicitly cast to 'Any?' with 'as Any?' to silence this warning
You can keep the value as-is (with 2 layers of optionality), hiding one of the layers of optionality behind the veil of Any
. Adding as Any?
doesn't actually do anything at runtime. It's syntactic salt that indicates that you intentionally wanted this behaviour, as opposed to mistakenly letting it slip by.
let optionalAny123: Any? = optionalOptionalAny as Any?
Ideally, you should try to flatten optionality as soon as you can. If the nestedness of optional carries some sort of significance in your system, act upon that as soon as you can, and flatten the optional shortly thereafter.
Upvotes: 7
Reputation: 353
I want to add an example about what rmaddy said.
Suppose we have the following code:
var dictionary: [String: Any] = ["coco": 1, "foo": "yo"]
func value(dic: [String: Any] , forKey key: String) -> Any {
// Expression implicitly coerced from 'Any?' to 'Any'
return dictionary[key]
}
// Optional(1)
print(value(dic: dictionary, forKey: "coco"))
At the beginning, even if we don't declare any optional value, the dictionary don't know if the key really exists. So, you can better understand why you have another optional value that you didn't expect.
Upvotes: 1
Reputation: 100543
Since type of dictionary
sub scripting value is Any?
with dictionary always append ?
to it's key/value
access = Any??
you can replace
[String: Any?]?
with
[String: Any]?
Upvotes: 3
Reputation: 318924
The value of your dictionary is Any?
. The result of accessing a dictionary value is an optional because the key might not exist. So you end up with Any??
.
There's really no need for your dictionary to be declared to have optional values.
Change it to [String: Any]?
and your issue goes away.
Upvotes: 7