Why does Array's contains(_:) method cause an error when comparing an optional value with a non-optional value in Swift?

I’m working with Swift and encountered an issue when using the contains method on an array. The following code works fine:

let result = ["hello", "world"].contains(Optional("hello")) // ✅ Works fine

However, when I try to use the same contains method with the array declared in a separate constant(or variable), I get a compile-time error:

let stringArray = ["hello", "world"]
let result = stringArray.contains(Optional("hello")) // ❌ Compile-time error

The compiler produces the following error message:

Cannot convert value of type 'Optional<String>' to expected argument type 'String'

Both examples seem conceptually similar, but the second one causes a compile-time error, while the first one works fine.

This confuses me because I know that Swift automatically promotes a non-optional value to an optional when comparing it with an optional value. This means "hello" should be implicitly converted to Optional("hello") for the comparison.


What I understand so far:

  • The contains(_:) method is defined as:

    func contains(_ element: Element) -> Bool
    

    Internally, it calls contains(where:), as seen in the Swift source code:

    🔗 Reference

  • contains(where:) takes a closure that applies the == operator for comparison.

  • Since Swift allows comparing String and String? directly (String is implicitly promoted to String? when compared with an optional), I expected contains(where:) to work the same way.


My Questions:

  1. Why does the first example work, but the second one fails with a compile-time error?
  2. What exactly causes this error in the second case, even though both cases involve comparing an optional value with a non-optional value?
  3. Does contains(_:) behave differently when used with an explicit array variable rather than a direct array literal? If so, why?

I know that there are different ways to resolve this, like using nil coalescing or optional binding, but what I’m really looking for is a detailed explanation of why this issue occurs at the compile-time level.

Can anyone explain the underlying reason for this behavior?

Answered by Claude31 in 831975022

My guess for the first is that the compiler in the first performs type inference and analyses ["hello", "world"] as array of optional.

let result = ["hello", "world"].contains(Optional("hello")) // ✅ Works fine

But in the second,

let stringArray = ["hello", "world"]
let result = stringArray.contains(Optional("hello")) // ❌ Compile-time error

you explicitly declare stringArray to be [String]. Then in second line, compiler balks at it.

Why does the first example work

I don’t know.

but the second one fails with a compile-time error? What exactly causes this error in the second case, even though both cases involve comparing an optional value with a non-optional value?

The declaration of contains that you posted shows that it takes an Element. Element is String. If you don’t pass a String, you’ll get an error. (The fact that it calls another contains method that would work with the optional doesn’t matter; it doesn’t get that far. You could argue that contains should not take an Element but rather an equality-comparable-with-Element; I think that’s possible.)

Does contains(_:) behave differently when used with an explicit array variable rather than a direct array literal? If so, why?

My guess is that by some magic the type of the array literal is determined to be an array of optional strings, but that really is just a guess.

Accepted Answer

My guess for the first is that the compiler in the first performs type inference and analyses ["hello", "world"] as array of optional.

let result = ["hello", "world"].contains(Optional("hello")) // ✅ Works fine

But in the second,

let stringArray = ["hello", "world"]
let result = stringArray.contains(Optional("hello")) // ❌ Compile-time error

you explicitly declare stringArray to be [String]. Then in second line, compiler balks at it.

Jessy wrote:

by making the array optional.

Well, not the array, but the contents of the array.

Like, for example

Yes. Or like this:

let a = ["Hello", "Cruel", "World!"] as [String?]
let cruel = "Cruel"
let cruelQ = Optional("Cruel")
print(a.contains(cruelQ)) // true
print(a.contains(nil)) // false
print(a.contains(cruel)) // true

ps It’s better to reply as a reply, rather than in the comments; see Quinn’s Top Ten DevForums Tips for this and other titbits.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Why does Array's contains(_:) method cause an error when comparing an optional value with a non-optional value in Swift?
 
 
Q