I've been experimenting with making Never
something closer to a true bottom type over the last few days, and wanted to start a discussion around some of the design decisions that have come up. Most of this post is inspired by some of the past discussions of Never
:
- [Pitch] Never as a bottom type
- SE-0215 - Conform Never to Equatable and Hashable - #2 by beccadax
- SE-0217 - The "Unwrap or Die" operator - #4 by DevAndArtist
The general consensus from past discussions seems to be:
- It's generally useful to be able to treat uninhabited types as a bottom type in expressions, despite the fact that it could confuse beginners
- It's unclear whether an uninhabited type should be capable of conforming to protocols with static or initializer requirements.
-
Currently,(edit: this isn't true in all cases). It is probably not desirable forB <: A
impliesB.Type <: A.Type
in SwiftNever.Type
to be a subtype of all meta types. Instead, as has been noted in past threads,Never
would be a subtype of all metatypes because it is a subtype of all types, andNever.Type
should have no special subtyping behavior.
Based on the above, I'd like to propose the following:
- All uninhabited types (including
Never
) should be convertible to any other type. This satisfies expression use cases likelet x = optional ?? fatalError("optional was nil!")
. It also allows us to remove the special case()->Never
->()->T
conversion which is allowed in single expression function and closure bodies. I've been hacking on an initial implementation of this which seems promising so far. Notably, conversion is weaker than subtyping. For example, uninhabited types could not be used as covariant return types of method overrides (). - All inhabited types should conform to any protocol which does not have static or initializer requirements(maybe except for static/init requirements w/
Self
params). I haven't investigated this as thoroughly so far, but with these limitations I believe it can be accomplished by taking advantage of some of the Sema changes to support tuple conformances. - Don't introduce any special behavior for metatypes of uninhabited types
Alternatives:
- Special case only
Never
instead of all uninhabited types, or add some kind of@bottomType
annotation. In my opinion this just adds unnecessary special case behavior. - Allow uninhabited types to conform to protocols with static/init requirements and synthesize trapping implementations. This potentially adds a lot of runtime complexity for very little gain. It does mean uninhabited types are not true bottom types, but there are few practical benefits.
- Try to establish a 'real' subtyping relation instead of just an implicit conversion. This doesn't seem to have many practical benefits, and it makes the metatype situation potentially more complicated.
Future Directions:
- Allow use of
throw
as aNever
-returning expression, for example,let x = optional ?? throw MyError()
this is fairly straightforward to implement, but is of limited usefulness without the changes above. - Also allow usage of
return
,break
, andcontinue
as expressions of typeNever
. These introduce some additional design challenges due to the use of autoclosures in the standard library.