9

I'm trying to assign a value to x from a function f that takes one parameter (a string) and throws.

The current scope throws so I believe a do...catch isn't required.

I'm trying to use try with the coalescing operator ?? but I'm getting this error: 'try' cannot appear to the right of a non-assignment operator.

guard let x = try f("a") ??
              try f("b") ??
              try f("c") else {
    print("Couldn't get a valid value for x")
    return
}

If I change try to try?:

guard let x = try? f("a") ??
              try? f("b") ??
              try? f("c") else {
    print("Couldn't get a valid value for x")
    return
}

I get the warning Left side of nil coalescing operator '??' has non-optional type 'String??', so the right side is never used and the error: 'try?' cannot appear to the right of a non-assignment operator.

If I put each try? in brackets:

guard let x = (try? f("a")) ??
              (try? f("b")) ??
              (try? f("c")) else {
    print("Couldn't get a valid value for x")
    return
}

It compiles but x is an optional and I would like it to be unwrapped.

if I remove the questions marks:

guard let x = (try f("a")) ??
              (try f("b")) ??
              (try f("c")) else {
    print("Couldn't get a valid value for x")
    return
}

I get the error Operator can throw but expression is not marked with 'try'.

I'm using Swift 4.2 (latest in Xcode at time of writing).

What is the correct way to do this to get an unwrapped value in x?

Update:* f()'s return type is String?. I think the fact that it's an optional string is important.

Josh Paradroid
  • 1,172
  • 18
  • 45
  • I think that you should start by removing duplicates and do something like `let x = ['a', 'b', 'c'].lazy.compactMap { try? f($0) }.first`. – Sulthan Mar 06 '19 at 11:05
  • 1
    A single `try` can cover the entire expression, e.g `guard let x = try f("a") ?? f("b") ?? f("c") else {...}` – Hamish Mar 06 '19 at 11:24
  • @Hamish Your suggestion fixes my error _and_ makes my code more readable. Thank you! Please feel free to post it as an answer. – Josh Paradroid Mar 06 '19 at 11:37
  • @Hamish Won't that stop on the first error though? I think it works differently than the original code. – Sulthan Mar 06 '19 at 11:47
  • @Sulthan The `try` keyword doesn't actually have any effect on a program's behaviour, the compiler just requires it in order to make error throwing explicit. No matter where you place the keyword, the function will exit upon the first thrown error. – Hamish Mar 06 '19 at 11:53
  • 1
    @JoshParadroid Sure thing! – Hamish Mar 06 '19 at 11:53
  • Related: [Function throws AND returns optional.. possible to conditionally unwrap in one line?](https://stackoverflow.com/questions/39691178/function-throws-and-returns-optional-possible-to-conditionally-unwrap-in-one-l) – Martin R Mar 06 '19 at 12:03
  • @hamish to make myself clear, it wont have the same functionality as the version with `try?` which will continue executing after errors. – Sulthan Mar 06 '19 at 12:24
  • 1
    @Sulthan Ah right, yup that's correct. Sorry, when you said "original code" I assumed you were referring to the first code block in OP's question. As I understand it, the `try` behaviour is the one OP wants. – Hamish Mar 06 '19 at 23:01

1 Answers1

8

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.

Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 2
    An alternative for Swift < 5 would be a (double) optional pattern: `guard case let x?? = try? f("a") ?? f("b") ?? f("c")` – Martin R Mar 06 '19 at 12:00