Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 892
Stephen Schaub Avatar asked Aug 01 '16 13:08

Stephen Schaub


People also ask

What is an optional string in Swift?

An optional in Swift is basically a constant or variable that can hold a value OR no value. The value can or cannot be nil. It is denoted by appending a “?” after the type declaration. For example: var tweet: String?

What is optional chaining and optional binding in Swift?

Optional binding stores the value that you're binding in a variable. 2. Optional chaining doesn't allows an entire block of logic to happen the same way every time. 2. Optional binding allows an entire block of logic to happen the same way every time.

Should I use optional chaining?

You can use optional chaining when attempting to call a method which may not exist. This can be helpful, for example, when using an API in which a method might be unavailable, either due to the age of the implementation or because of a feature which isn't available on the user's device.

How optional is implemented in Swift?

Optionals: Swift introduced optionals that handle the absence of a value, simply by declaring if there is a value or not. An optional is a type on it's own! enum: An enumerated type is a data type consisting a set of members of the type.

What is optional chaining in Swift?

Multiple queries can be chained together, and the entire chain fails gracefully if any link in the chain is nil. Optional chaining in Swift is similar to messaging nil in Objective-C, but in a way that works for any type, and that can be checked for success or failure.

Why does optional chaining return two values?

Optional chaining return two values − Since multiple queries to methods, properties and subscripts are grouped together failure to one chain will affect the entire chain and results in 'nil' value. Optional chaining is specified after the optional value with '?' to call a property, method or subscript when the optional value returns some values.

What is optional chaining in Java?

Optional chaining is a process for querying and calling properties, methods, and subscripts on an optional that might currently be nil.

What is the difference between optional chaining and multiple queries?

Since multiple queries to methods, properties and subscripts are grouped together failure to one chain will affect the entire chain and results in 'nil' value. Optional chaining is specified after the optional value with '?' to call a property, method or subscript when the optional value returns some values. Optional Chaining '?'


2 Answers

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’s that the original call is optional, so 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 characters and count properties will return a value; otherwise, 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 your 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 in 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 can’t call count on it.

In order to call the count property on (s?.characters), the expression needs to be optionally unwrapped, i.e. 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)
like image 132
Abizern Avatar answered Oct 14 '22 04:10

Abizern


On the Swift-users mailing list, Ingo Maier was kind enough to point me to the section on optional chaining expressions in the Swift language spec, which states:

If a postfix expression that contains an optional-chaining expression is nested inside other postfix expressions, only the outermost expression returns an optional type.

It continues with the example:

var c: SomeClass?
var result: Bool? = c?.property.performAction()

This explains why the compiler wants s?.characters.count in my example above, and I consider that it answers the original question. However, as @Martin R observed in a comment, there is still a mystery as to why these two expressions are treated differently by the compiler:

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

If I am reading the spec properly, the subexpression

(s?.characters) 

is "nested inside" the overall postfix expression

(s?.characters).count

and thus should be treated the same as the non-parenthesized version. But that's a separate issue.

Thanks to all for the contributions!

like image 33
Stephen Schaub Avatar answered Oct 14 '22 02:10

Stephen Schaub