<html><head><meta http-equiv="Content-Type" content="text/html charset=utf-8"></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div><blockquote type="cite" class=""><div class="">On 21 Feb 2017, at 00:34, Karl Wagner &lt;<a href="mailto:karl.swift@springsup.com" class="">karl.swift@springsup.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><meta http-equiv="Content-Type" content="text/html charset=utf-8" class=""><div style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class=""><br class=""><div class=""><blockquote type="cite" class=""><div class="">On 19 Feb 2017, at 21:04, Anton Zhilin &lt;<a href="mailto:antonyzhilin@gmail.com" class="">antonyzhilin@gmail.com</a>&gt; wrote:</div><br class="Apple-interchange-newline"><div class=""><div dir="ltr" class=""><div class="markdown-here-wrapper" style=""><p style="margin:0px 0px 1.2em!important" class="">It’s expected that if you need resilience, then you will throw an “open” enum. Essentially, we pass resilience of typed <code style="font-size:0.85em;font-family:Consolas,Inconsolata,Courier,monospace;margin:0px 0.15em;padding:0px 0.3em;white-space:pre-wrap;border:1px solid rgb(234,234,234);background-color:rgb(248,248,248);border-radius:3px;display:inline" class="">throws</code> on to those who will hopefully establish resilience of enums.</p><p style="margin:0px 0px 1.2em!important" class="">If you prefer separate error types, then declare a base protocol for all your error types and throw a protocol existential. You won’t even need default case in switches, if closed protocols make it into the language.</p><p style="margin:0px 0px 1.2em!important" class="">I don’t like any solution that is based on comments. I think that compiler should always ignore comments.</p><div class=""><br class=""></div></div></div></div></blockquote><br class=""></div><div class="">Open enums can only add cases, not remove them. That means that new versions of a function will similarly only be able to add errors, and won’t be able to communicate that certain errors are no longer thrown. Protocols aren’t a solution, because you will need to write verbose conformances for all of your Error types.</div><div class=""><br class=""></div><div class="">Let me put this in another (perhaps more palatable) way. Forget comments, let’s say its part of some hidden metadata:</div><div class=""><br class=""></div><div class="">- The compiler lists every error which can be thrown by a function (it’s easily able to do that)</div><div class="">- Rather than making this part of the signature, the signature only says “throws an Error” and the actual Error list is written somewhere in the module as documentation/metadata.</div><div class=""><br class=""></div><div class="">Here’s why it’s so good:</div><div class=""><br class=""></div><div class="">- It’s completely free (you don’t write anything). The compiler generates everything for you.</div><div class="">- It’s **completely optional**: it won’t make your structure your Errors in a way that's less usable in a type-system sense for clients who don’t care about exhaustive catching.</div><div class="">- Exhaustive catching within your own module for free (you can omit a catch-all, the compiler won’t complain)</div><div class=""><br class=""></div><div class="">It’s good for resiliency, too:</div><div class=""><br class=""></div><div class="">- Non-resilient functions always reserve the right to throw new Errors in later versions. No system will get exhaustive catching for them anyway.</div><div class="">- If you stop throwing a specific Error, nothing breaks - it simply vanishes from the documentation/metadata. The compiler can simply warn about the redundant catch.</div><div class="">- Resilient (@versioned) functions can still offer exhaustive catching if we want to offer that. We might decide to make that opt-in/opt-out, because it would mean they would be limited to removing Errors, and never adding new ones.</div><div class="">—&gt; As with any ABI promise, we will trap or it will be UB if you break the contract. We couldn’t validate it when compiling, but theoretically a validator could be built which compared two library versions.</div><div class=""><br class=""></div><div class="">- Karl</div><div class=""><br class=""></div></div></div></blockquote></div><div class=""><br class=""></div>So here’s my counter-proposal, fleshed out:<div class=""><br class=""></div><div class="">Specially, on resiliency:<br class=""><div class=""><br class=""></div><div class="">## Internally to a module</div><div class=""><br class=""></div><div class="">Compiler can use generated Error-list metadata to:</div><div class="">- provide compile-errors with specific uncaught errors (better diagnostics)</div><div class="">- allow omitting catch-alls</div><div class="">- optimise away Error existential allocations</div><div class=""><br class=""></div><div class="">All of that would automatically apply to all throwing/rethrowing functions, without any additional developer effort.</div><div class=""><br class=""></div><div class="">## Cross-module</div><div class=""><br class=""></div><div class="">Compiler can use generated Error-list metadata to:</div><div class="">- Inform users about errors that might get thrown by this version of the function (purely documentation)</div><div class="">- Allow omitting catch-alls for specific functions which opt-in to that contract.</div><div class=""><br class=""></div><div class="">And that’s it. Notice there is no behavioural change; the Error-list metadata is entirely optional.</div><div class=""><br class=""></div><div class="">### Exhaustive catching cross-module</div><div class=""><br class=""></div><div class="">Resilient functions can _additionally_ promise to never throw new Errors. It should be an additional promise. From an error-list perspective, the function makes an additional promise that later versions of the error-list will never get more inclusive.</div><div class=""><br class=""></div><div class="">We can’t check that resilience at compile-time, though. If you change the function signature, you will get a error in the dynamic linker. Similarly, if somebody just adds a case to their @fixed enum, you won’t know until runtime when it gets thrown and nobody’s there to catch it.</div><div class=""><br class=""></div><div class="">- It would be cool if we failed gracefully in that case; if the caller wasn’t catching exhaustively, the new error should fall in to the existing catch-all.</div><div class="">- Otherwise, if the caller was assuming the library author kept their promise and omitted a catch-all, we should still synthesise one to provide a unique trap location (something like swift_resilience_unexpectedError()).</div><div class="">&nbsp; &nbsp; - It means we can't optimise away the Error existential cross-module (maybe in -Ounchecked?), but that seems to me like an acceptable cost.</div><div class=""><br class=""></div><div class="">The big problem with this is that it relies on unwritten, generated metadata. For resilient functions promising resilient error-lists, it’s helpful to have the errors you’re promising written down and easily manually inspectable. That’s why I initially suggested having the compiler validate the documentation comments. We could still do that - so if you have a @versioned function and you additionally say that the errors it throws are also @versioned, you have to write a comment listing every Error (and the compiler will check it).</div><div class=""><br class=""></div><div class=""><br class=""></div><div class="">Small example:</div><div class=""><br class=""></div><div class=""><font face="Courier" class="">enum FailureReason {</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; case deviceBusy</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; case networkDown</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; case notFound</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><br class=""></div><div class=""><font face="Courier" color="#9a244f" class="">//% [Error-list]: FailureReason.notFound</font></div><div class=""><font face="Courier" class="">func openFile(_ path: String) throws { … }</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" color="#9a244f" class="">//% @versioned [Error-list]: FailureReason.notFound, FailureReason.deviceBusy</font></div><div class=""><font face="Courier" class="">@versioned(includingErrors)</font></div><div class=""><font face="Courier" class="">func read(_ path: String, range: Range&lt;Int&gt;) -&gt; Data {&nbsp;… }</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" color="#9a244f" class="">//% [Error-list]: &lt;rethrows from arg1&gt;</font></div><div class=""><font face="Courier" class="">func retrying(attempts n: Int, _ work: ()throws -&gt; Void) rethrows -&gt; Bool {</font></div><div class=""><font face="Courier" class="">&nbsp; for _ in 0..&lt;attempts {</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; do &nbsp; &nbsp; &nbsp;{ try work(); return true }</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; catch FailureReason.deviceBusy &nbsp; &nbsp;{ continue }</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; catch FailureReason.networkDown { continue }</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; catch { throw error }</font></div><div class=""><font face="Courier" class="">&nbsp; }</font></div><div class=""><font face="Courier" class="">&nbsp; return false</font></div><div class=""><font face="Courier" class="">}</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" color="#9a244f" class="">//% [Error-list]: &lt;rethrows from arg0, masks: FailureReason.notFound&gt;</font></div><div class=""><div class=""><font face="Courier" class="">func mustBeFound(_ work: ()throws -&gt; Void) rethrows {</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; do { try work() }</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; catch FailureReason.notFound { fatalError(“This thing must be found") }</font></div><div class=""><font face="Courier" class="">&nbsp; &nbsp; catch { throw error }</font></div><div class=""><font face="Courier" class="">}</font></div></div><div class=""><br class=""></div><div class=""><font face="Courier" class="">mustBeFound { openFile(“test.txt”) } // can be proven not to throw (in same module), because mustBeFound handles all errors</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// cross-module</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// Function does not have a versioned error-list; catch-all is mandatory.</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><div class=""><font face="Courier" class="">do { try openFile(“test.txt”) }</font></div><div class=""><font face="Courier" class="">catch .notFound { print(“not found!”) }</font></div></div><div class=""><font face="Courier" class="">catch &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { print(“other error: \(error}”) }</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><div class=""><div class=""><font face="Courier" class="">do &nbsp; &nbsp;{ try mustBeFound { open(“test.txt”) } }</font></div></div><div class=""><font face="Courier" class="">catch { print(“other error: \(error}”) }</font></div></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">// Function has a versioned error-list; catch-all is&nbsp;optional.</font></div><div class=""><font face="Courier" class=""><br class=""></font></div><div class=""><font face="Courier" class="">do &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;{ try readFile(“test.txt”, range: 0..&lt;64) }</font></div><div class=""><font face="Courier" class="">catch .notFound &nbsp; { print(“file not found!”) }</font></div><div class=""><font face="Courier" class="">catch .deviceBusy { /* maybe retry? */ &nbsp; &nbsp; &nbsp; }</font></div><div class=""><font face="Courier" color="#9a244f" class="">// [implicit] catch { _swift_reslience_unexpectedError() }</font></div><div class=""><font face="Courier" color="#9a244f" class=""><br class=""></font></div><div class=""><br class=""></div></div></body></html>