Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Swift 6のTyped throwsとSwiftにおけるエラーハンドリングの全体像を学ぶ

Swift 6のTyped throwsとSwiftにおけるエラーハンドリングの全体像を学ぶ

Swiftがオープンソースになって以来、長年議論され続けてきたTyped throwsの提案がついに承認されました。これは、throws節にエラー型を指定できる新しい言語機能であり、Swift 6から導入されます。

Swiftにおけるエラーハンドリングの考え方は、Swift 2でthrows/tryが導入される際にまとめられた"Error Handling Rationale and Proposal"というドキュメントに基づいています。その後、Swift 5でResultが導入され、Swift 5.5でSwift Concurrencyが追加されました。これにより、それらの機能とエラーハンドリングを併せて考える必要が生じました。そして、Swift 6でTyped throwsが導入されます。

しかし、それらの変化は、Swiftにおけるエラーハンドリングの基本的な考え方を変えるものではありません。"Error Handling Rationale and Proposal"の理念は今も生き続けています。

本セッションでは、まずTyped throwsについて説明し、次に"Error Handling Rationale and Proposal"の内容を復習します。最後に、それらを踏まえた上でSwift 6でどのようにエラーハンドリングを行うべきか、その全体像を示します。

iOSDC Japan 2024: レギュラートーク(40分)
https://fortee.jp/iosdc-japan-2024/proposal/c48577a8-33f1-4169-96a0-9866adc8db8e

Yuta Koshizawa

August 23, 2024
Tweet

More Decks by Yuta Koshizawa

Other Decks in Technology

Transcript

  1. !LPIFS J04%$+BQBOࢀՃྺ 2016:ෆࢀՃ   2017:۩ମྫͱΫΠζͰֶͿɺ4XJGUͷछྨͷΤϥʔͷ࢖͍෼͚   2018:J04ΤϯδχΞͷͨΊͷɺ4XJGU͔Β1ZUIPOͷϥΠϒϥϦΛ࢖ͬͯػցֶश͢Δํ๏ 

     2019:)FBSUPG4XJGU   2020:ਓͰΞϓϦΛϦϑΝΫλϦϯάͯ͠ݟ͖͑ͯͨɺ࠷ڧͷJ04ΞϓϦઃܭʹٻΊΒΕΔ͜ͱ   2021:BTZODBXBJU΍BDUPSͰJ04ΞϓϦ։ൃ͕Ͳ͏มΘΔ͔#FGPSF"GUFSͷ۩ମྫͰֶͿ   2022:4XJGU$PODVSSFODZ࣌୅ͷJ04ΞϓϦͷ࡞Γํ   2023:"*࣌୅ʹੜ͖ΔJ04ΤϯδχΞͷา͖ํ  ⛔3FKFDUFE 2024:4XJGUͷ5ZQFEUISPXTͱ4XJGUʹ͓͚ΔΤϥʔϋϯυϦϯάͷશମ૾ΛֶͿ   ˞ ಺͸ఏग़ͨ͠ϓϩϙʔβϧͷ਺ʢ೥͸GPSUFFʹه࿥͕ͳ͘ਖ਼֬ͳ਺͸ෆ໌ʣ
  2. SE-0413: Typed throws1 • Status: Implemented (Swi3 6.0) 1 h$ps:/

    /github.com/swi3lang/swi3-evolu:on/blob/main/proposals/0413-typed-throws.md
  3. Untyped throws func parseInt(_ value: String) throws -> Int try

    parseInt("") // ۭจࣈྻ try parseInt("ABC") // ෆਖ਼ͳϑΥʔϚοτ try parseInt("9999999999999999999") // ൣғ֎
  4. Untyped throws func parseInt(_ value: String) throws -> Int {

    if value.isEmpty { throw ParseError.emptyValue } guard let number = Int(value) else { if value.contains(/^[-]?\d+$/) { throw ParseError.outOfBounds(value) } else { throw ParseError.illegalFormat(value) } } return number }
  5. Untyped throws do { let number: Int = try parseInt(string)

    print(number) } catch let error as ParseError { switch error { // ParseError case .emptyValue: ... case .illegalFormat(let value): ... case .outOfBounds(let value): ... } }
  6. Untyped throws do { let number: Int = try parseInt(string)

    print(number) } catch let error as ParseError { switch error { // ParseError case .emptyValue: ... case .illegalFormat(let value): ... case .outOfBounds(let value): ... } }
  7. Untyped throws do { let number: Int = try parseInt(string)

    print(number) } catch let error as ParseError { switch error { // ParseError case .emptyValue: ... case .illegalFormat(let value): ... case .outOfBounds(let value): ... } } catch { preconditionFailure("Never reaches here.") }
  8. Typed throws do { let number = try parseInt(string) print(number)

    } catch { switch error { // ParseError ... } }
  9. Untyped throws do { let number: Int = try parseInt(string)

    print(number) } catch let error as ParseError { switch error { // ParseError case .emptyValue: ... case .illegalFormat(let value): ... case .outOfBounds(let value): ... } } catch { preconditionFailure("Never reaches here.") }
  10. Typed throws do { let number: Int = try parseInt(string)

    print(number) } catch { switch error { // ParseError case .emptyValue: ... case .illegalFormat(let value): ... case .outOfBounds(let value): ... } }
  11. Typed throws͕΄͔ͬͨ͠ଞͷཧ༝ func foo() throws -> Int { // any

    Error let result: Result<Int, FooError> = ... switch result { case .success(let value): return value case .failure(let error): throw error // FooError } } • Result, Task ౳ͱͷม׵ͰΤϥʔͷܕ͕ࣦΘΕΔ
  12. SE-0413: Typed throws1 • Status: Implemented (Swi3 6.0) • Alterna:ves

    considered • Thrown error type syntax 1 h$ps:/ /github.com/swi3lang/swi3-evolu:on/blob/main/proposals/0413-typed-throws.md
  13. ΋͠ () ͕ඞਢͰͳ͔ͬͨΒ func foo() throws (E) -> Int {

    ... } • ͲͪΒͱղऍ͢Ε͹ྑ͍͔ᐆດ • throws E • throws ((E) -> Int)
  14. ΋͠ () ͕ඞਢͰͳ͔ͬͨΒ func foo() throws bar -> Int {

    ... } struct bar: Error {} • bar ͕ܕ͔effect͔۠ผͰ͖ͳ͍
  15. SE-0413: Typed throws1 • Status: Implemented (Swi3 6.0) • Alterna:ves

    considered • Mul:ple thrown error types 1 h$ps:/ /github.com/swi3lang/swi3-evolu:on/blob/main/proposals/0413-typed-throws.md
  16. ΋͠ () ͷதʹෳ਺ͷΤϥʔܕΛॻ͚ͨΒ func foo() throws(BarError, BazError) -> Int •

    BarError | BazError Λಋೖ͢Δͷͱಉ͡ • Typed throwsͷͨΊʹ΍Δʹ͸ܕγεςϜ΁ͷӨڹ͕େ͖͢ ͗Δ
  17. ݸਓతʹ͸Τϥʔܕ͸ҰͭͰྑ͍ͱࢥ͏ • ΤϥʔܕΛෳ਺ࢦఆͰ͖Δͱɺ҆қʹԼ૚ͷΤϥʔΛͦͷ·· throw ͕ͪ͠ func foo() throws(IOError, NetworkError, ...)

    -> Int • Τϥʔͷܕ͕ҰͭͳΒɺͦͷؔ਺ʹͱͬͯͷΤϥʔͱ͸Կ͔Λ ߟ͑Δ͖͔͚ͬʹͳΔ • Լ૚ͷΤϥʔΛͦͷ·· throw ͢ΔͳΒ any Error Ͱྑ͍
  18. do do { let number = try parseInt(string) // ParseError

    print(number) } catch { // ParseError }
  19. do do { let number = try parseInt(string) // ParseError

    try foo(number) // FooError } catch { // ??? }
  20. do do { let number = try parseInt(string) // ParseError

    try foo(number) // FooError } catch { // any Error }
  21. do throws do throws(ParseError) { let number = try parseInt(string)

    // ParseError try foo(number) // ⛔ FooError } catch { // ParseError }
  22. Result.get // Swift 5 let result: Result<Int, Never> = ...

    do { let value = try result.get() } catch { preconditionFailure("Never reaches here.") }
  23. rethrows // try͕ෆཁ [2, 3, 5].map { $0 * $0

    } // try͕ඞཁ try ["2", "3", "5"].map { try parseInt($0) }
  24. rethrows extension Sequence { func map<T>( _ transform: (Element) throws

    -> T ) rethrows -> [T] // rethrows͸↓ͷೋͭͱಉ͡Α͏ͳ΋ͷ func map<T>(_ transform: (Element) -> T) -> [T] func map<T>( _ transform: (Element) throws -> T ) throws -> [T] }
  25. rethrows // try͕ෆཁ [2, 3, 5].map { $0 * $0

    } // try͕ඞཁ try ["2", "3", "5"].map { try parseInt($0) }
  26. Typed throwsʹΑΔ rethrows extension Sequence { func map<T, E: Error>(

    _ transform: (Element) throws(E) -> T ) throws(E) -> [T] }
  27. SE-0413: Typed throws1 • Status: Implemented (Swi3 6.0) • When

    to use typed throws • Typed throwsΛ҆қʹ࢖͏΂͖Ͱͳ͍ཧ༝ • Ͳ͏͍͏ͱ͖ʹTyped throwsΛ࢖͏΂͖͔ 1 h$ps:/ /github.com/swi3lang/swi3-evolu:on/blob/main/proposals/0413-typed-throws.md
  28. Ͳ͏͍͏ͱ͖ʹTyped throwsΛ࢖͏΂͖͔ • Ϟδϡʔϧ΍ύοέʔδͷதʹดͯ͡ΤϥʔϋϯυϦϯά͢Δ Α͏ͳ৔߹ • map ͷ rethrows ΍

    Result ͷΑ͏ʹͦΕࣗମ͕ΤϥʔΛੜΈ ग़͞ͳ͍৔߹ • Existen*al͕࢖͑ͳ͍ʗΦʔόʔϔου͕ڐ༰Ͱ͖ͳ͍৔߹
  29. Resist the tempta+on to use typed throws1 Typed throwsΛ࢖͏༠࿭ʹ఍߅͍ͯͩ͘͠͞ 1

    h$ps:/ /github.com/swi3lang/swi3-evolu:on/blob/main/proposals/0413-typed-throws.md
  30. Even with the introduc/on of typed throws into Swi5, the

    exis/ng (untyped) throws remains the be>er default error-handling mechanism for most Swi5 code.1 typed throws͕Swi.ʹಋೖ͞Εͨͱͯ͠΋ɺطଘͷʢuntypedʣ throws͸ɺ΄ͱΜͲͷSwi.ίʔυʹ͓͍ͯɺґવͱͯ͠ΑΓྑ͍ σϑΥϧτͷΤϥʔϋϯυϦϯάϝΧχζϜͰ͢ɻ 1 h$ps:/ /github.com/swi3lang/swi3-evolu:on/blob/main/proposals/0413-typed-throws.md
  31. Typed throwsͷΞϯνύλʔϯ enum AppError { case network(NetworkError) case server(ServerError) case

    cancellation(CancellationError) } • ⛔ Typed throwsʹ͢ΔͨΊʹ CancellationError Λ case ʹՃ͑ΔͳͲɺSwi0ͷඪ४తͳΤϥʔϋϯυϦϯάͷ׳ྫͱઓ ͏
  32. Typed throwsͷΞϯνύλʔϯ func foo() throws { // any Error try

    bar() // AppError try baz() // OtherError } do { try foo() } catch is CancellationError { // Ωϟϯηϧ͞Εͨ৔߹ͷॲཧ } catch { // ⛔ AppError.calcellation͕ͬͪ͜ʹؚ·ΕΔ }
  33. Error Handling Ra-onale and Proposal5 • Kinds of error •

    Propaga0on 5 h$ps:/ /github.com/swi3lang/swi3/blob/main/docs/ErrorHandlingRa<onale.md
  34. Kinds of error • Simple domain errors • Recoverable errors

    • Universal errors • Logic failures
  35. Simple domain errors guard let number = Int(string) else {

    // ΤϥʔϋϯυϦϯά } • ൃੜ৚͕݅໌֬Ͱ͙͢ʹϋϯυϦϯά͞ΕΔΑ͏ͳΤϥʔ • Τϥʔͷཧ༝͕໌֬ͳͷͰ nil Ͱද͞ΕΔ • ͙͢ʹϋϯυϦϯά͠ͳ͍ͱݪҼෆ໌ʹ • ϋϯυϦϯά͸৚݅෼ذͰे෼
  36. Simple domain errors guard let last = array.popLast() else {

    // ΤϥʔϋϯυϦϯά } guard let max = array.max() else { // ΤϥʔϋϯυϦϯά }
  37. Recoverable errors do { try data.write(to: url, options: .atomic) }

    catch { // ΤϥʔϋϯυϦϯά } • ݪҼ͕ଟذʹ౉ΔΤϥʔͰ Error Ͱද͞ΕΔ • ద੾ͳ৔ॴ·ͰΤϥʔΛ఻೻ͤ͞ݪҼʹԠͯ͡ϋϯυϦϯά • ઐ༻ͷߏจʢ throws ΍ do/try/catch ʣ͕΄͍͠
  38. Universal errors Array(repeating: 42, count: .max) // Out of memory

    • ݪҼͱͳΔՕॴ͕ଟ༷Ͱ͋ΒΏΔՕॴͰى͜ΓಘΔΤϥʔ • ϓϩηεͷதஅɾϝϞϦෆ଍ɾελοΫΦʔόʔϑϩʔͳͲ • ݱঢ়Ͱ͸ඪ४తͳํ๏ͰϋϯυϦϯά͢Δ͜ͱ͸Ͱ͖ͳ͍
  39. Logic failures nilValue! // Unexpectedly found nil while unwrapping array[-1]

    // Index out of bounds Int.max + 1 // Overflow • ίʔυͷޡΓ͕ݪҼͰൃੜ͢ΔΤϥʔ • Forced unwrappingͷࣦഊɾΠϯσοΫεൣғ֎ɾ੔਺ͷΦʔ όʔϑϩʔͳͲͷࣄલ৚݅ҧ൓ͳͲ • ಈతʹϋϯυϦϯά͢ΔͷͰ͸ͳ͘ίʔυΛमਖ਼ͯ͠ରԠ
  40. Logic failures do { let number: Int = try parseInt(string)

    print(number) } catch let error as ParseError { switch error { // ParseError case .emptyValue: ... case .illegalFormat(let value): ... case .outOfBounds(let value): ... } } catch { preconditionFailure("Never reaches here.") }
  41. Simple domain errors vs Logic failures // Simple domain errors

    dictionary[key] // ࣦഊ͢Δͱnil // Logic failures array[i] // ࣦഊ͢Δͱ࣮ߦ࣌Τϥʔ
  42. array[i] ͕ൣғ֎ʹͳΔଟ͘ͷέʔε͸ίʔυͷޡΓ // i, j͕ൣғ֎ʹͳͬͨ͜ͱ͕Θ͔ͬͯ΋࣮ߦ࣌ʹͰ͖Δ͜ͱ͸ͳ͍ func sort(_ array: inout [Int])

    { for i in 0 ..< array.count { for j in i + 1 ..< array.count { if array[j] < array[i] { let t = array[j] array[j] = array[i] array[i] = t } } } }
  43. Kinds of error • Simple domain errors • Recoverable errors

    • Universal errors • Logic failures
  44. Manual propaga+on func foo() -> Foo? { ... guard let

    value = Int(string) else { return nil // manual } ... }
  45. Manual propaga+on func foo() -> Result<Foo, any Error> { ...

    switch result { case .success(let value): ... case .failure(let error): return .failure(error) // manual } ... }
  46. Automa'c propaga'on func foo(x: Int, y: Int) throws -> Int

    { let a = try bar(x) let b = try baz(y) return a + b }
  47. Manual propaga+on func foo(x: Int, y: Int) -> Result<Foo, any

    Error> { bar(x).flatMap { a in baz(y).map { b in a + b } } }
  48. do ه๏ foo :: Int -> Int -> Either FooError

    Int foo x y = do a <- bar x b <- baz y return (a + b)
  49. Swi$ͷΤϥʔϋϯυϦϯά Kind \ Propaga-on Manual Automa-c Simple domain Optional -

    Recoverable Result throws Universal - Ϋϥογϡ Logic - Ϋϥογϡ
  50. ΤϥʔͷݪҼ͸όά ʢίʔυΛमਖ਼͢΂͖໰୊ʣ ΤϥʔͷݪҼ͕໌֬ͰҰ͚ͭͩ ʢݪҼผʹ෼ذ͕ඞཁͳ͍ʣ ΤϥʔͷܕΛࢦఆ͍ͨ͠ ※ ຊ౰ʹඞཁ͔ཁݕ౼ Logic failure precondition౳

    Simple domain error Optional ΤϥʔͷܕΛݶఆͯ͠ྑ͍৚݅Λ ຬͨ͢ ΤϥʔΛม਺౳Ͱѻ͏ඞཁ͕͋Δ ※ ຊ౰ʹඞཁ͔ཁݕ౼ ΤϥʔΛม਺౳Ͱѻ͏ඞཁ͕͋Δ ※ ຊ౰ʹඞཁ͔ཁݕ౼ Recoverable / Automatic / Typed throws(FooError) Recoverable / Manual / Typed Result<Foo, FooError> Recoverable / Manual / Untyped Result<Foo, any Error> Recoverable / Automatic / Untyped throws YES NO ΤϥʔͷܕΛݶఆͯ͠ྑ͍৚݅ • Ϟδϡʔϧ΍ύοέʔδͷதʹดͯ͡Τϥʔ ϋϯυϦϯά͢ΔΑ͏ͳ৔߹ • map ͷ rethrows ΍ Result ͷΑ͏ʹͦΕ ࣗମ͕ΤϥʔΛੜΈग़͞ͳ͍৔߹ • Existential͕࢖͑ͳ͍ʗΦʔόʔϔου͕ڐ༰ Ͱ͖ͳ͍৔߹