Using Nil-Coalescing Operator with Try? for Function That Throws and Returns Optional

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. a nil).
  • 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



Leave a reply



Submit