So we better drop the magic catch because they involve a different behaviour than the old catch behaviour.
So the best thing the compiler has to do here is to infer all the types that can be inferred inside the do block and with that information provide the user with the tools (maybe autocompletion) to help him writing the needed concrete catch blocks.
While I understand the desire for backwards compatibility, it seems odd to me that the error will be typed if it escapes the function, but not if caught within the function.
Also, a more general question: what about closures? Can they throw typed errors?
Totally agree, thatâs why I first suggest all throw statements to be typed, then have some places, e.g., annotated type, not infer the common type. It should maintain all src compat, but you need to annotate them when handling typed error with a single catch block, which is a hassle, to put it mildly.
I think that we can consider catch to be akin to a default in a switch: even when the type itâs well-defined default still behaves as a general catch-all (I understand the difference as that doesnât capture a variable).
Though even without a magic catch youâll still have source breakage with the exclusivity check on thrown errors, and you cannot remove that otherwise the feature becomes useless
Something along that line. IMO unannotated catch should be reserved for untyped handling. It is much easier to explain that way, and seems to lead to a more coherent system. Swift types are know to get very long, though.
A couple notesâin the section on generic throws parameters, IMO it would be more consistent with Swift's existing generics system for
func foo<T>(_ block: () throws T -> Void) rethrows T
to be a compilation error, rather than raising the error at the call site (i.e, we should require the T: Error constraint). E.g., if this were instead written as:
func foo<T>(_: T.Type, _ block: (() throws T -> Void)?) rethrows T
would it be an error to write:
foo(Int.self, nil)
?
Also, regarding this point:
f({ throw E.failure }) // closure gets inferred to be `() throws E -> Void` so this will compile fine
as I noted in a comment above, this inference of the throws type is not, in general, source compatible. Is the intent that this inference only takes place in the context of a generic throws type? If so, that should be made very explicit.
There's no way that T does not conform to Error because it is annotated after the throws statement, so it is verbose IMO.
It is compatible because it is the block which is being inferred to be E, we were talking about the inference inside the empty catch clause. That function behaves as any other generic function where the type is inferred automatically.
correct, but this can be open for discussion when the pitch is finished next week
Is there precedent in the language for this sort of implicit generic constraint? My mental model for generics right now is that if I supply a concrete type which satisfies the generic constraints, the call will compile. I'm wary about breaking this model. E.g., it feels wrong to me that the following compiles just fine:
func foo<T>(_: T.Type, _: () throws T -> Void) rethrows T { ... } // Compiles fine
If you're limiting this only to contexts where you are inferring the throws type for a generic throws T parameter, then yes, this is source compatible (since there are no such throws T declarations today, of course).
However, if this inference is meant to be more broad, it is not source compatible:
struct Foo: Error { ... }
struct Bar: Error { ... }
var throwers = [{ throw Foo() }] // Inferred as `Array<() throws -> ()>`, or `Array<() throws Foo -> ()>`?
throwers.append({ throw Bar() }) // Compiles today, error if we infer `throws Foo`
Just read the new proposal document. A function can list only one throw type. So if I had a function that channeled two closures, each with its own error type, what am I supposed to do? I would want to list both in the new function's list. I could hope that the two arguments have the same type, or one is a subtype of the other; otherwise, the new function would need to list either Error or a general throws.
Should you mention function sub-typing. A non-throwing function is a subtype with any throwing function (with the compatible parameters & return-type). A typed-throws function is a subtype of a general throwing function. Two typed-throws functions have a subtype relationship if the corresponding thrown types have a subtype relationship. (If we add support for multiple typed-throws, then the various superset rules apply.)