1

I looked for this article, but it does not cover my case.

If i understand correct, we can use try either in do..catch.. statement or in a function that can throw.

But sometimes i see something like:

let jsonData = try jsonEncoder.encode(employee1)

Where jsonData is not optional. What is meaning of this? What if try sttement fail? Why value is not optional? Can someone explain? Thanks.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Evgeniy Kleban
  • 6,794
  • 13
  • 54
  • 107

2 Answers2

5

In addition to the cases you mentioned, you can call try at top-level code. Here is a simple self-contained example:

// main.swift:
enum MyError : Error {
    case failed
}

func foo() throws -> Int   {
    throw MyError.failed
}

defer { print("Good bye.") }

let x = try foo()
print(x)

You can compile and run this as a Xcode "Command Line Project" or directly from the command line:

$ swiftc main.swift

$ ./main
Good bye.
Fatal error: Error raised at top level: main.MyError.failed: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-900.0.74.1/src/swift/stdlib/public/core/ErrorType.swift, line 187
Illegal instruction: 4

The failed try in the top-level code causes the program to terminate with an error message. Deferred statement (if present) will be executed however.

This is slightly different from using a forced try! statement, which causes the program to abort as well, but immediately, without executing deferred statements. (This can be relevant if deferred statements are used to clean-up resources, e.g. remove temporary files).


The error message originates from ErrorType.swift, line 187:

/// Invoked by the compiler when code at top level throws an uncaught error.
@_inlineable // FIXME(sil-serialize-all)
@_silgen_name("swift_errorInMain")
public func _errorInMain(_ error: Error) {
  fatalError("Error raised at top level: \(String(reflecting: error))")
}

(also observed in Non Exhaustive List When Handling Errors Inside a Class Function in Swift).

Apparently the top-level code behaves as if embedded in a do-catch block:

do {
    func foo() throws -> Int   {
        throw NSError(domain: "foo", code: 0, userInfo: nil)
    }

    defer { print("Good bye.") }

    let x = try foo()
} catch {
    fatalError("Error raised at top level: \(String(reflecting: error))")
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
1

A function call marked with try like the one you posted, as you stated, must be located inside a function marked with the keyword throws or a do/catch statement or it will cause a compile error.

On a side note if you want to call a throwing function outside a do/catch or another throwing function you can use try? before your function call to get an optional result that will be nil if something is thrown. There is also the try! variant that assumes nothing will be thrown returning a non-optional result, but if something is thrown the app will crash.

Marco Boschi
  • 2,321
  • 1
  • 16
  • 30
  • please try to use try without body of throwing func and it will compile, like my code in playground. – Evgeniy Kleban Jan 03 '18 at 18:00
  • @EvgeniyKleban That's a special case, Swift will `fatalError` if an error is thrown and is not handled at the top level, compare https://stackoverflow.com/q/43109871/2976878 – Hamish Jan 03 '18 at 18:02
  • @Hamish what is difference with try! and try in such cases? – Evgeniy Kleban Jan 03 '18 at 18:05
  • 1
    @EvgeniyKleban Really not that much of a difference. Although a failed `try!` invokes `preconditionFailure` rather than `fatalError` (https://github.com/apple/swift/blob/master/stdlib/public/core/ErrorType.swift#L179). – Hamish Jan 03 '18 at 18:08
  • @Hamish in both cases app will crash? – Evgeniy Kleban Jan 03 '18 at 18:16
  • @EvgeniyKleban `fatalError` terminates the program unconditionally, `preconditionFailure` terminates the program except in `-Ounchecked` builds in which case it's assumed to never be triggered and so is optimised out (breaking this assumption is a programmer error). Though it's generally inadvisable to use `-Ounchecked` builds anyway due to their inherent lack of safety. – Hamish Jan 03 '18 at 18:18
  • @Hamish thank you for detailed explanation. If i understand correct, i rather do try? or try! if i ever want to do try statement outside of do..catch.. or function that throws :) – Evgeniy Kleban Jan 03 '18 at 18:21
  • @EvgeniyKleban In the case you mention, it really just comes down to preference; it could well be argued that using `try!` at the top-level instead of `try` more clearly signals that the program will be terminated if an error is thrown. Though you couldn't, for example handle some errors and leave others uncaught, e.g `do { try someFunction() } catch SomeError.someCase { ... }` at the top-level. If the function throws `SomeError.someCase`, that's handled and the program will continue. If any other error is thrown, the program will terminate. `try!` on the other hand terminates regardless. – Hamish Jan 03 '18 at 18:27
  • @Hamish: I just discovered that there is also a difference with respect to deferred statements. – Martin R Jan 03 '18 at 18:53
  • @MartinR Hah, nice find! It does indeed appear to be as you suggest; top-level code is implicitly nested in a `do - catch` where the `catch` calls the built-in `errorInMain`. From quickly looking at SILGen, [one can see](https://github.com/apple/swift/blob/9da4373601b0826bb259159c05f4f3c6bd3c9d44/lib/SILGen/SILGen.cpp#L1255) `prepareEpilog` being called with `true` being passed to the `isThrowing` param, so that emits a "rethrow epilog", which looks like a `catch` block to me. – Hamish Jan 03 '18 at 19:18
  • The call to `errorInMain` is [inserted in the cleanup](https://github.com/apple/swift/blob/9da4373601b0826bb259159c05f4f3c6bd3c9d44/lib/SILGen/SILGen.cpp#L1305). – Hamish Jan 03 '18 at 19:18
  • @Hamish: You provided quite a lot of information here – I'll happily delete my answer if you post your own. – Martin R Jan 03 '18 at 19:19
  • @MartinR I don't currently have the time to write up a full blown answer (believe it or not, I'm actually supposed to be working :P). Though even if I did, I'm not sure whether it'd be better to extend my existing answer to the above linked Q&A or not. But anyway, I'm more than happy for you to include any of my above points in your answer if you wish (in general, I'm happy for anyone to include something I've said in a comment in their answer) :) If not, I'll happily write an answer/extend existing one when I get some time. – Hamish Jan 03 '18 at 19:44