Seems strange. My mental model for never is it never returns. Void seems better here.
Isnât that because a bare return
statement or exiting the function scope without a return
actually does return an empty tuple (AKA Void
)?
As far as I can tell, a function that exits every of its paths with a throw
has no implicit return
.
There are two options from where I see the return type can be provided from:
- explicit but unreachable return
- explicit type for the whole closure which cancels the whole type inference
None of there two options are present in the edge case we're discussing.
My whole point is, that the current inference seems to provide a wrong return type in this case. It does feel like a bug. However it would be a bug that probably would need to go through evolution, hence this thread.
If the core team joins the conversation and tells that there is nothing we can / should do about it, so be it.
Today, return type inference for multi-statement closures does not look at code in the closure body at all. If the closure does not have a type annotation or other type context (e.g. the closure is an argument in a function call where the parameter type provides context), the return type is simply defaulted to Void
.
Iâm not sure I understand. Youâre saying that a closure that returns something else (say an Int
for example) is treated as if its return type were Void
if it doesnât have any type annotations?
I tried this in Playgrounds and it wouldnât compile, complaining that it could not determine the type and wanted an explicit annotation. I donât have access to XCode at the moment to see what it says, but how would one create the closure with no annotations and an inferred return type that @hborla mentions?
let closure = {
let value = 42
return value
} //Error: Unable to infer complex closure return type;
// add explicit type to disambiguate
Hereâs an example:
let closure = {
let a = 1
print(a)
}
closure()
Thereâs no type annotation, so type inference defaults the return type to Void
. It doesnât compile if you return a different type of value, because without type context, the only return type inference supported is the default Void
type. Like I mentioned upthread, return type inference from statements in a multi-statement closure body is a work in progress.
Okay, I think I understand now. The only instance of a closure that has a different return type than itâs actual return type should be whatâs actually the subject of this Pitch: A multiline closure where all paths never return.
I see that type(of:)
returns () -> Never
for this closure
{
fatalError()
}
yet it returns () -> ()
AKA () -> Void
when you add an irrelevant line such as in this closure
{
let _ = 0
fatalError()
}
so I think this illustrates both what youâre explaining to me and the issue that precipitated this Pitch.
This is because of Swiftâs implicit returns from single-expression closures.
{
fatalError()
}
turns into
{
return fatalError()
}
so it assumes a return type of () -> Never
because of fatalError()
âs return type of Never
.
On the other hand,
{
let _ = 0
fatalError()
}
isnât a single-expression closure, so the compiler assumes the default return type of () -> ()
(a.k.a. () -> Void
).
protocol InstantiableAndConvertableToInt { init() func makeInt() -> Int } func instantiateAndMakeInt<I: InstantiableAndConvertableToInt>(from type: I.Type) -> Int { I().makeInt() } let integer = instantiateAndMakeInt(from: Never.self)
How would this work? Would it crash (which may be unexpected)?
Even if Never
becomes the bottom type, this still would not compile.
let integer = instantiateAndMakeInt(from: Never.self)
This line uses the metatype Never.Type
(Never.self
) which is not a bottom type. Secondly, making Never
the bottom type does not mean that it implicitly conforms to all protocols. These points are spelled out in the original pitch:
Also, while with this new subtyping rule
Never
becomes a subtype of all metatypes,Never.Type
does not become a subtype of all metatypes because unlikeNever
, it is inhabited.Additionally, this proposal intentionally does not automatically conform
Never
to additional protocols. The core team's acceptance notes from SE-0215 do a good job of expressing why this isn't yet being addressed:However, being a bottom type does not imply that Never should implicitly conform to all protocols. Instead, convenient protocol conformances for Never should be added as deemed useful or necessary.
...
With respect to protocol conformances, the Core Team felt the language has clearly moved in a direction where explicit protocol conformance is fundamental to the language model. The compiler has grown affordances to make the implementation of protocol conformances easy in many cases (e.g., synthesized implementations of Hashable) but that the explicit protocol conformance is quite important. Adding rules for implicit protocol conformances â something that has been considered in Swiftâs history â ends up adding real complexity to the language and its implementation that can be hard to reason about by a user as well as by the language implementation.
I see â I was under the impression that Never
as a bottom type meant that it would conform to all protocols.
(Side note: the metatype isnât actually used in the functionâs code â just in its parameters. The metatype is used by the function to specify the I
type parameter. Since I
would resolve to Never
and the function instantiates I
, it would be instantiating a value to type Never
â at least, if Never
conformed to InstantiableAndConvertableToInt
. This pattern is used elsewhere in Swift (like here and here).)
I do think Never
as the bottom type would actually be helpful (though I think it should be applied to all uninhabited types if possible).
(though I think it should be applied to all uninhabited types if possible).
I disagree. Owen addressed this as well, and I agree with his argument:
Make all trivially and/or structurally uninhabited types equivalent to the bottom type
One option which has been considered in the past is treating every uninhabited type as equivalent to the bottom type. This makes sense from a logical perspective, but it has the potential to significantly complicate typechecking and has few practical benefits. Additionally, enums with no cases are widely used as a namespacing mechanism, and are not intended to be used as the bottom type. Structurally uninhabited types (tuples with uninhabited element types, structs with uninhabited stored property types, etc.) introduce additional complications, like determining whether a type is equivalent to the bottom type across a resilience boundary. This proposal recommends that
Never
and any typealiases thereof remain the sole representation of the bottom type in Swift.