Search code examples
swiftstringfoundation

Why does String.contains behave differently when I import Foundation?


Just started learning Swift, am really confused about the following behaviour.

This is what I get when I run String.contains without Foundation:

"".contains("") // true
"a".contains("") // true
"a".contains("a") // true
"" == "" // true

And this is what I get with Foundation:

import Foundation

"".contains("") // false
"a".contains("") // false
"a".contains("a") // true
"" == "" // true

Why are the results different depending on whether I import Foundation? Are there other such differences, and is there an exhaustive list somewhere? Didn't find anything in the Foundation documentation, but this seems important to document. I'm only aware of this other example.

Also: How does this happen and is it normal? I understand that Swift has stuff like extensions that change the behaviour of every instance of something once they're included, but surely that should only add behaviour, not change existing behaviour. And if existing behaviour is changed, shouldn't the language indicate this somehow, like make me use a different type if I want the different behaviour?


Solution

  • Basically this is the same as the question I answer here.

    Foundation is not part of Swift, it's part of Cocoa, the older Objective-C library that preceded Swift by many, many years. Foundation's version of a string is NSString. But Swift String is "bridged" to NSString, so as soon as you import Foundation, a bunch of NSString methods spring to life as if they were part of Swift String even though they are not. In your case, you actually end up calling a completely different method which, as you've discovered, gives different results.

    A good way to see this is to command-click on the term contains in your code (or even better, option-click it and then click Open in Developer Documentation):

    • If you have not imported Foundation (or UIKit), you jump to Swift String's contains.
    • If you have imported Foundation, you jump to Foundation's contains.

    As for this part:

    shouldn't the language indicate this somehow

    I'm afraid Stack Overflow is not very good on "should" questions. I would say, yes, this is a little maddening, but it's part of the price we pay for the easy and seamless integration of Swift into the world of Cocoa programming. You could argue that the Swift people should not have named their method contains, but that train has left the station, and besides, it's a name that perfectly indicates what the method does.

    Another thing to keep in mind is that you would probably never really use Swift except in the presence of Foundation (perhaps because you're in the presence of UIKit or SwiftUI or AppKit) so in practical terms the issue wouldn't arise. You've hit an unusual edge-case, which is commendable but, ex hypothesi, unusual.


    To make things even more complicated, I think the Swift library method you encountered may have been just introduced as part of Xcode 14 and Swift 5.7 etc. See https://developer.apple.com/videos/play/wwdc2022/110354/?time=1415 for the WWDC '22 discussion of new String features. In earlier versions of Xcode, the phrase "a".contains("") would not even have compiled in the absence of Foundation — and so the problem would never have arisen!