Stephen Schaub Stephen Schaub - 2 months ago 25
Swift Question

Optional chaining with Swift strings

With optional chaining, if I have a Swift variable

var s: String?


s might contain nil, or a String wrapped in an Optional. So, I tried this to get its length:

let count = s?.characters?.count ?? 0


However, the compiler wants this:

let count = s?.characters.count ?? 0


My understanding of optional chaining is that, once you start using
?.
in a dotted expression, the rest of the properties are made optional and are typically accessed by
?.
, not
.
.

So, I dug a little further and tried this in the playground:

var s: String? = "Foo"
print(s?.characters)
// Output: Optional(Swift.String.CharacterView(_core: Swift._StringCore(_baseAddress: 0x00000001145e893f, _countAndFlags: 3, _owner: nil)))


The result indicates that
s?.characters
is indeed an Optional instance, indicating that
s?.characters.count
should be illegal.

Can someone help me understand this state of affairs?

Answer

When you say:

My understanding of optional chaining is that, once you start using '?.' in a dotted expression, the rest of the properties are made optional and are typically accessed by '?.', not '.'.

I would say that you are almost there.

It's not that all the properties are made optional, it that the original call is optional so it it looks like the other properties are optional.

characters is not an optional property, and neither is count, but the value that you are calling it on is optional. If there is a value, then the the characters and count properties will return a value, If it is nil, then nil is returned. It is because of this that the result of s?.characters.count returns an Int?

If either of the properties were Optional, then you would need to add ? to it, but in this case, they aren't. So you don't.

Edited Following Comment

From the comment:

I still find it strange that both s?.characters.count and (s?.characters)?.count compile, but (s?.characters).count doesn't. Why is there a difference between the first and the last expression?

I'll try and answer it here where there is more room than the comment field:

s?.characters.count

If s is nil, the whole expression returns nil, otherwise an Int. So the return type is Int?

(s?.characters).count // Won't compile

Breaking this down. If s is nil, then (s?.characters) is nil so we cant call count on it.

In order to call the count property on (s?.characters) the expression needs to be optionally unwrapped so it is correctly written as:

(s?.characters)?.count

Edited to add further

The best I can get to explaining this is with this bit of playground code:

let s: String? = "hello"

s?.characters.count
(s?.characters)?.count
(s)?.characters.count
((s)?.characters)?.count

// s?.characters.count
func method1(s: String?) -> Int? {
    guard let s = s else { return nil }

    return s.characters.count
}

// (s?.characters).count
func method2(s: String?) -> Int? {
    guard let c = s?.characters else { return nil }

    return c.count
}

method1(s)
method2(s)