Thursday, November 2, 2017

What does Never return type - Swift Functions?

Leave a Comment

What does the func with return type Never do?

For example:

func addNums() -> Never {  //my code  } 

What will be the difference if I kept the return type nothing as follows?

func addNums() -> Void {  //my code  } 

Edit

Suppose I wish to handle a fatalError as said by dpassage, below code will be sufficient.

print("its an error") return 

Apple explanation on Never type is

The return type of functions that don't explicitly specify a return type

This was not a duplicate question from SO Post, I wish to know more detailed answer need details like:

  1. Practical examples on the difference between both Never and Void return types

  2. Means in which condition we adopts the return types. Also there is a chance the return type can be nil. Need a comparison of that feature too

The answer should focus on the differences.

4 Answers

Answers 1

This one 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

func crashAndBurn() -> Never {     fatalError("Something very, very bad happened") } 

Usage specifics and advantages over @noreturn, as referenced by Erica Sadun:

  • Never allows a function or method to throw: e.g. () throws -> Never. Throwing allows a secondary path for error remediation, even in functions that were not expected to return.
  • As a first class type, Never works with generics in a way that the @noreturn attribute could not.
  • Never proactively prevents a function from claiming both a return type and no-return at the same time. This was a potential issue under the old system.

Practical usage ultimately comes down to developer/compiler convenience:

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     } } 

Answers 2

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.

Answers 3

Void

Void is itself a return type which is tupple with zero element. You can use Void and () interchangeably.

Look at this examples,

  1. func yourFunc() {} This is function without return type, which basically returns tupple with zero element, that can be written as ()

  2. func yourFunc() -> Void {} Function which is explicitly informing compiler about return type of void

  3. func yourFunc() -> () {} This return type of () displays same as void type. () indicates tupple with zero element

Never

Never return-type informs compiler that even no need to return empty tuple () also as function with never return type is used for the exit point for current execution like crash, fatal error, abort or exit.

For detail understanding of never, let's have a look at 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 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 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."

Answers 4

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment