Yeah I can see your points. Then maybe there is no easy way for this.
I figure that typed rethrows needs to be more ceremonious than untyped ones. I also mentioned that we need to minimize the common case that simply throws the argument errors. Maybe:
rethrows(argument)
That's why I think it may be better to go with only untyped rethrows
for the time being. It seems we still have plenty room for future extension, whether to simply reinterpret rethrows (in a source breaking manner), or add typed rethrows.
If we're to retain source compat, it needs to be the former.
I guess first step for me, would be to make this generic.
Because it's inherent generic. Not in all cases But in the common ones I would say. And with the special ones repeating the error would be ok maybe.
Then we would just not be able to throw typed e.g. from a map?
This would be quite a disappointment in my opinion.
I'd leave rethrows as it is now and just reinterpret such as throwing members that inherently doesn't throw (Never) and it is source compatible and, as you said, maybe reinterpret rethrows or even getting it deprecated from the language.
Simple as that. We already need to specify:
- subtyping relationship,
- type inference behaviour,
- do-block behaviour,
- unannotated rethrows behaviour.
It's already a sizable proposal.
Sure but it takes away some of the major arguments in favour of this proposal.
May main point is, that we can't leave out the details of one topic, because they can be a deal breaker for the whole thing often?
When I think about this, it's not entirely true. For the simple case of rethrows
, we still have throws Never
. It would just affect the case, where rethrows
throws a different error than the closure.
Because this only affects very few use cases, I would be okay with it.
I would even take this one step further:
func foo<E>(_ bar: () throws E -> ()) rethrows CustomError<E>
And now, if we can somehow make CustomError<Never>
to be equal or equivalent to Never
, then we pretty much donāt need rethrows
at all.
If CustomError
has a stored property of type E
, than CustomError<Never>
is uninhabited. What if allow to call without try
functions which throw not only Never
, but any uninhabited type?
I thought of something like that for a while now, but it is definitely overkill for this proposal. We cannot completely replace rethrows
. Maybe we can put this into Future Directions?
But what about rethrowing an unrelated error UnrelatedError()
?
So wouldn't that be enough for the start? Or would at force us into a wrong direction?
func foo(closure: () throws -> ()) rethrows
func foo(closure: () throws -> ()) rethrows IntError
func foo(closure: () throws SomeError -> ()) rethrows
func foo(closure: () throws SomeError -> ()) rethrows IntError
func foo<E: Error>(closure: () throws E -> ()) rethrows E
rethrows
: Throw Error
if parameter throws
rethrows T
: Throw T if parameter throws
This seems the most logical and is also in sync with how throws
functions.
Back to Array.map
I guess we need to find a way to update the Standard Library without a lot of friction for the users.
And for that, we need to solve the throw
issue.
I guess the cleanest non source compat solution would be, if we could update source in Swift 6 from throw FooError()
to throw FooError() as Error
for all throw
that happen directly in a do
block or in closure that has it's type inferred.
With that we would solve the throw
type inference issue as close as possible to its source.
Assuming the standard library functions are not converting the parameter errors to other errors, we could update them to the generic error type versions (speaking about, map
etc.)
With that source change, we could even use general catch handlers with specific inferred errors.
Found the post in the old thread why I had the impression that there was some issue with rethrows
where it must remain untyped:
Honestly, I personally donāt see any. It would be cool if we could replace the keyword as in a typed throws world it can be misleading, but thatās not a priority to me.
Bikeshedding alarm, not necessarily something to include into the proposal unless it gains some traction:
// assuming E: Error and F: Error
() -> Void === () throws<Never> -> Void
() throws -> Void === () throws<Error> -> Void
() throws<E> -> Void
// replacing `rethrows` with `throws?`
// requires `try` if closure throws anything other than `Never`
// and just like above it would be a shorthand that throws `Error`
( () throws<E> -> Void ) throws? -> Void === ( () throws<E> -> Void ) throws?<Error> -> Void
// requires `try` if closure throws anything other than `Never`
// but it would throw a custom type `F`
( () throws<E> -> Void ) throws?<F> -> Void
// requires `try` if closure throws anything other than `Never`
// and the user has the impression that it just rethrows `E`
( () throws<E> -> Void ) throws?<E> -> Void
This makes the model simpler, at least from my point of view:
throw
always requirestry
throw?
(renamedrethrows
) only requirestry
if one closure parameter throws anything else thanNever
- error type rules and all shorthands would be consistent between both variants, hence
throws? == throws?<Error>
One more note. There is no way to control the conditional rethorwing mechanism to explicitly specify which closure parameter it should track if there are many.
More bike shedding:
Instead of throws?<...>
we'd add a configurable parameter throws<...>(...)
.
(() throws -> Void, () throws -> Void) throws<ErrorType>(.always)-> Void
// same as
(() throws -> Void, () throws -> Void) throws<ErrorType> -> Void
// tracks only first parameter
(() throws -> Void, () throws -> Void) throws<ErrorType>(.parameter(1)) -> Void
// tracks all, which means if one of them is throwable, the compiler will require `try`
(() throws -> Void, () throws -> Void) throws<ErrorType>(.all) -> Void
- Do we need this much control around
rethrows
? - Has anyone ever wanted something like this already?
- Can we solve some existing problems with this?
Not really a problem of this proposal. You could say the same about (A) -> A
where A
has a really long name.
Thatās different, because rethrows
throws the same error as the argument in ~90-95% of the cases, while itās not that common to have a function return the same type as its only argument.
The point is not my example of a function returning the same type as the parameter, but that you donāt want to type the error type manually which will be required unless you want to keep it (re)throwing Error
. The length of the type name isnāt an issue of the pitched feature as it trivially already exists in other places.
Yeah thatās why we already discarded the thought of having rethrows
default to something different than Swift.Error
some posts above.