Typed throws

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. :thinking:

Because it's inherent generic. Not in all cases :smiley: 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.

1 Like

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.

1 Like

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. :flushed::heart_eyes:

Found the post in the old thread why I had the impression that there was some issue with rethrowswhere 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 requires try
  • throw? (renamed rethrows) only requires try if one closure parameter throws anything else than Never
  • 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?
1 Like

Not really a problem of this proposal. You could say the same about (A) -> A where A has a really long name.

1 Like

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.

1 Like