Using nil-coalescing operator with try? for function that throws and returns optional
Essentially, this has to do with the grammar of the try
operator. When used with a binary expression without brackets, try
applies to the whole binary expression, so this:
try? machine.itemCode(code: 0) ?? "Unknown"
is the same as:
try? (machine.itemCode(code: 0) ?? "Unknown")
Since itemCode
throws an error, the latter part of the expression ?? "Unknown
is ignored, and the try?
expression evaluates to nil.
On the other hand, the second expression is like this:
(try? machine.itemCode(code: 0)) ?? "Unknown"
The try?
expression is evaluated first (to nil), then the ??
is applied, evaluating the whole expression to "Unknown".
How can I use try with the coalescing operator?
A single try
can cover the entire expression, so you can just say:
guard let x = try f("a") ?? f("b") ?? f("c") else {
print("Couldn't get a valid value for x")
return
}
Same goes for try?
:
guard let x = try? f("a") ?? f("b") ?? f("c") else {
print("Couldn't get a valid value for x")
return
}
Although note that in Swift 4.2 x
will be String?
due to the fact that you're applying try?
to an already optional value, giving you a doubly-wrapped optional which guard let
will only unwrap one layer of.
To remedy this, you could coalesce to nil
:
guard let x = (try? f("a") ?? f("b") ?? f("c")) ?? nil else {
print("Couldn't get a valid value for x")
return
}
But in Swift 5 this is unnecessary due to SE-0230, where try? f("a") ?? f("b") ?? f("c")
will be flattened into a single optional value automatically by the compiler.
It does not trigger any errors when apply the Nil-Coalescing operator on a none optional variable
The nil-coalescing operator
public func ??<T>(optional: T?, @autoclosure defaultValue: () throws -> T) rethrows -> T
takes an optional a the first operand. So a ?? b
and a != nil ? a! : b
are equivalent provided that a
is an optional.
That is not the case in your example
let a: Int = 3, b: Int = 4
a ?? b
The first operand a
is not an optional. However, the compiler can
"wrap" a non-optional value T
into an optional T?
in order
to match a function or operator. For example, in
func foo(x: Int?) { }
foo(3)
the argument 3
is wrapped into an Int?
.
In your case, the expression is equivalent to
Optional<Int>.Some(a) ?? b
which is equivalent to
Optional<Int>.Some(a) != nil ? Optional<Int>.Some(a)! : b
However, the compiler is not so smart to recognize that Optional<Int>.Some(a)
cannot be nil
.
Function throws AND returns optional.. possible to conditionally unwrap in one line?
Update: As of Swift 5, try?
applied to an optional expression does not add another level of optionality, so that a “simple” optional binding is sufficient. It succeeds if the function did not throw an error and did not return nil
. val
is then bound to the unwrapped result:
if let val = try? getSomething() {
// ...
}
(Previous answer for Swift ≤ 4:) If a function throws and returns an optional
func getSomething() throws -> Value? { ... }
then try? getSomething()
returns a "double optional" of the
type Value??
and you have to unwrap twice:
if let optval = try? getSomething(), let val = optval {
}
Here the first binding let optval = ...
succeeds if the function did
not throw, and the second binding let val = optval
succeeds
if the return value is not nil
.
This can be shortened with case let
pattern matching to
if case let val?? = try? getSomething() {
}
where val??
is a shortcut for .some(.some(val))
.
Type inference fails when using nil-coalescing operator with two optionals
You've given the compiler too many options, and it's picking the wrong one (at least not the one you wanted). The problem is that every T
can be trivially elevated to T?
, including T?
(elevated to T??
).
someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber
Wow. Such types. So Optional. :D
So how does Swift begin to figure this thing out. Well, someNumber
is Double?
, so it tries to turn this into:
Double? = Double?? ?? Double?
Does that work? Let's look for a generic mapped
, starting at the most specific.
func mapped<T where T:SomeProtocol>(dictionary: NSDictionary?, key: String) -> T? {
To make this work, T
has to be Double?
. Is Double?:SomeProtocol
? Nope. Moving on.
func mapped<T>(dictionary: NSDictionary?, key: String) -> T? {
Does this work? Sure! T
can be Double?
We return Double??
and everything resolves.
So why does this one work?
someNumber = self.mapped(dictionary, key: "someNumber") ?? someNumber!
This resolves to:
Double? = Optional(Double? ?? Double)
And then things work the way you think they're supposed to.
Be careful with so many Optionals. Does someNumber
really have to be Optional? Should any of these things throw
? (I'm not suggesting throw
is a general work-around for Optional problems, but at least this problem gives you a moment to consider if this is really an error condition.)
It is almost always a bad idea to type-parameterize exclusively on the return value in Swift the way mapped
does. This tends to be a real mess in Swift (or any generic language that has lots of type inference, but it really blows up in Swift when there are Optionals involved). Type parameters should generally appear in the arguments. You'll see the problem if you try something like:
let x = test.mapped(...)
It won't be able to infer the type of x
. This isn't an anti-pattern, and sometimes the hassle is worth it (and in fairness, the problem you're solving may be one of those cases), but avoid it if you can.
But it's the Optionals that are killing you.
EDIT: Dominik asks a very good question about why this behaves differently when the constrained version of mapped
is removed. I don't know. Obviously the type matching engine checks for valid types in a little different order depending on how many ways mapped
is generic. You can see this by adding print(T.self)
to mapped<T>
. That might be considered a bug in the compiler.
What is the cleanest way in swift to use an optional in a function parameter when nil should return a specific default value?
var anOptionalInt: Int?
let aSpecificDefaultReturnValue: Float = 0.99
let result = anOptionalInt.map { iTake(aNonOptional: $0) } ?? aSpecificDefaultReturnValue
You can use the fact that the Swift Optional
enum has a map
function, and couple it with the nil coalescing operator:
Optional.map
looks inside your optional 'box' and (if it finds something in the box) uses the transform you supply to alter your value, or ignores the transform if you have nothing inside the box (i.e. anil
).- The nil coalescing operator
??
basically says 'return everything left of me if that thing is not-nil, otherwise return everything right of me'.
Swift return or throw
I don't think you can use null coalesce
in a return statement. However, you can do something like this
guard let res = calcSomething() else { throw err.invalidSomething }
return res
Related Topics
How to Add a Show More/Show Less Uibutton to Control Uitextview
How to Move Application's Window Between Virtual Desktops in Os X
Type of Expression Is Ambiguous Without More Context in Xcode 11
File Couldn't Be Opened Because You Don't Have Permission to View It Error
How to Create a Hotspot Network in iOS App Using Swift
How to Synchronize Coredata with Webservices in Swift
Format Println Output in a Table
Switching a @State Property to a @Binding Property Interferes with Animation
How to Replace the Values of Labels in iOS-Charts
Swift Sprite Kit in App Purchase
Property with '= {Return}()' or '{Return}'
Swift Firebase Using an Unspecified Index
Passing Dynamic Int Variable from One Class to Another Class in Swift
How to Load Nsview from Xib with Swift 3
Tvos Remote Notification Replacement
Realmswift Initialize List:Cannot Specialize a Non-Generic Definition