What does a func
with return type Never
do?
For example:
func addNums() -> Nev
To better understand Never
and Void
, and how Never
is useful in more contexts than the old @noreturn
was, let's first look at what the two types actually are defined as:
Never
is defined here as:
public enum Never {}
Since there is no way to instantiate a value of an empty enum, the type system guarantees that no instance of Never
can exist. This means functions that specify their return type as Never
are prevented by the type system from actually returning under any circumstances.
The compiler takes this into account when doing control-flow analysis. For example, these two functions both compile without error, whereas they would fail if a function that returns Void
was substituted for fatalError
:
func foo(fail: Bool) -> String {
if fail {
fatalError()
} else {
return "foo"
}
// notice there is no return statement here
}
func bar(fail: Bool) -> Void {
let s: String
if fail {
fatalError()
// the compiler doesn't complain s is not initialized here
} else {
s = "bar"
}
print(s)
}
Void
is defined here as:
public typealias Void = ()
There are no two different instances of an empty tuple. Thus, the return value of functions returning Void
holds no information.
You can actually write return ()
or return Void()
. You can also use the "value" returned, like this:
func empty() -> Void {}
let v = empty()
print(type(of: v)) // prints "()"
although the compiler will warn "Constant 'v' inferred to have type 'Void', which may be unexpected".
Defining both Never
and Void
in terms of the type system rather than as special language features enables us to do some pretty clever things with generics. Let's look at an example of a Result
type, generic over both the success and failure type.
enum Result<R, E> {
case success(R)
case failure(E)
}
A possible specialization of this would be Result<Void, MyError>
. This would mean you have a result that, on success, does not hold any information beyond the fact it succeeded.
Another possibility could be Result<String, Never>
. This result is guaranteed by the compiler to never be the failure case.
Optionals interact with Never
and Void
in a similar way. Never?
can only ever be nil, and Void?
only holds the information wether it is nil or not, nothing more (it's basically a more complicated Bool). Both of these are not very useful on their own, but might appear when Never
or Void
are used as generic parameters somewhere.
In practice, you will rarely write functions returning Never
. I have personally used it to wrap fatalError
to create a function I use to mark functions that are not implemented yet:
func unimplemented(f: String = #function) -> Never {
fatalError("\(f) is not implemented yet")
}
Another example of a function returning Never
is dispatchMain()
, which can be used in command-line utilities to start the DispatchQueue.main
. Since this queue then waits for new blocks, dispatchMain()
never returns.
Void
Void is itself a return type which is a tuple with zero elements. You can use Void and () interchangeably.
Look at these examples,
func yourFunc() {}
This is a function without a return type, which basically returns a tuple with zero elements, that can be written as ()
func yourFunc() -> Void {}
Function which is explicitly informing the compiler about return type of void
func yourFunc() -> () {}
This return type of () displays the same as void type. () indicates a tuple with zero elements
Never
Never return-type informs the compiler that no need exists to return an empty tuple (). Also, function with the never return type is used for the exit point of the current execution like a crash, fatal error, abort or exit.
For a detailed understanding of never, let's have a look at an abort() example :
1.
func yourFunc() {
abort()
print("Will not reach at this point") //Warning for this line
}
2.
func yourFunc() -> Int {
if true {
abort()
} else {
return 1
}
}
From the above code snippets, we can see when we call abort() (which doesn't return a value) as the last statement in a function that expects a value to be returned (in our case Int). The compiler doesn't generate a warning.
abort()
public func abort() -> Never
Similarly for exit():
public func exit(_: Int32) -> Never
The apple documentation says: "Use Never as the return type when declaring a closure, function, or method that unconditionally throws an error, traps, or otherwise does not terminate."
So if you want to write a custom function that logs a catastrophic error, you should use the return type Never to signal to the compiler:
func catastrophicErrorDisplay(error: String) -> Never {
DisplaySomeCustomLogFacility(error)
}
In short "Never is used for sudden and total failure from which recovery is impossible."
Never
indicates that the function will never return. It's intended to be used for things like fatalError
which cause your program to crash intentionally, often after logging an error. You probably shouldn't use it unless you're doing something like making a handler for catastrophic errors in your application.
This is different from a function which just doesn't return a value, as in your second snippet. You could also write that as func addNums() -> Void
.
Never
return type was introduced in Swift 3 to substitute @noreturn
key.
See justification in this proposal:
SE-0102 Remove @noreturn attribute and introduce an empty Never type
As official documentation explains:
The return type of functions that do not return normally; a type with no values.
Use Never as the return type when declaring a closure, function, or method that unconditionally throws an error, traps, or otherwise does not terminate.
Source: https://developer.apple.com/documentation/swift/never
Basic illustration:
// The following function is our custom function we would use
// to manually and purposefully trigger crash. In the logs,
// we can specify what exactly went wrong: e.g. couldn't cast something,
// couldn't call something or some value doesn't exist:
func crashApp() -> Never {
fatalError("Something very, very bad happened! Crash the app!")
}
Usage specifics and advantages over @noreturn
, as referenced by Erica Sadun:
First note (regarding secondary error remediation) is probably particularly important. Never
function can have complex logic and throw – not necessarily crash.
Let's see some interesting use cases and comparison between Never
and Void
Example 1
func noReturn() -> Never {
fatalError() // fatalError also returns Never, so no need to `return`
}
func pickPositiveNumber(below limit: Int) -> Int {
guard limit >= 1 else {
noReturn()
// No need to exit guarded scope after noReturn
}
return rand(limit)
}
Example 2
func foo() {
abort()
print("Should not reach here") // Warning for this line
}
Example 3
func bar() -> Int {
if true {
abort() // No warning and no compiler error, because abort() terminates it.
} else {
return 1
}
}
abort()
is defined as:
public func abort() -> Never
These examples would not have been possible with it returning Void
:
public func abortVoid() -> Void {
fatalError()
}
func bar() -> Int {
if true {
abortVoid() // ERROR: Missing return in a function expected to return 'Int'
} else {
return 1
}
}
And to pack it up with abort()
returning Never
:
func bar() -> Int {
if true {
abort() // No ERROR, but compiler sees it returns Never and warns:
return 2 // Will never be executed
} else {
return 1
}
}
We use Void
to tell compiler there is no return value. Application keeps running.
We use Never
to tell compiler there is no return to caller site. Application runloop is terminated.