Mattt, the author of “zhongWJ”, the author of “zhongWJ”, the author of “zhongWJ”. Proofreading: NUMbbbbb, PMST; Finalized: Forelax

From our first article on nil in Objective-C to our recent peek at the Never type in Swift, “nonexistence” has been a topic of discussion in NSHipster. But today’s post is probably the one with the most ghastly white space detail of them all — because we focus on Void in Swift.

What is Void? In Swift, it is nothing more than an empty tuple.

typealias Void = ()
Copy the code

We’re going to start looking at it when we use Void.

let void: Void = ()
void. // No code completion prompt
Copy the code

Void values have no members: no member methods, no member variables, not even names. It’s not much more than nil. For an empty container, Xcode doesn’t give us any code completion hints.

Things made for nonexistence

In the standard library, the most notable and curious use of the Void type is in the Express Siblebynilliteral protocol.

protocol ExpressibleByNilLiteral {
    init(nilLiteral: ())
}
Copy the code

Types that comply with the Express Bynilliteral protocol can be initialized with nil literals. Most types do not follow this protocol, because it is easier to understand that the value may not exist as Optional. But every once in a while you come across express By general.

The specified constructor for ExpressibleByNilLiteral does not accept any actual arguments. (Assuming they did, what would be the result?) However, the protocol’s specified constructor can’t just be an empty constructor init(), because many types use it as the default constructor.

You can try to solve this problem by changing the specified constructor to a Type Method that returns nil, but some of the forced internally visible states are not available outside the constructor. A better solution here is to add a nilLiteral tag with a Void parameter to the constructor. This clever use of existing functionality to achieve unconventional results.

How do you compare things that don’t exist

Tuples along with mettypes (such as int. Type, where int. self returns the result), function types (such as (String) -> Bool), and existential types (such as Encodable & Decodable) make up the informal types. Unlike the formal or named types that comprise most of Swift, informal types are defined relative to other types.

Informal types cannot be extended. Void is an empty tuple, and since tuples are informal types, you cannot add methods, properties, or protocol compliance to Void.

extension Void {} // The informal type 'Void' cannot be extended
Copy the code

Void does not comply with the Equatable protocol because it cannot do so. However, when we call the equal operator (==), it works as expected.

void == void // true
Copy the code

The following global function, defined outside all formal protocols, implements this seemingly contradictory behavior.

func= =(lhs: (a), rhs: ()) -> Bool {
    return true
}
Copy the code

The less than operator (<) is treated in the same way as an alternative to the Comparable protocol and its derivative comparison operators.

func < (lhs: (), rhs: ()) -> Bool {
    return false
}
Copy the code

The Swift standard library provides an implementation of comparison functions for tuples of up to 6 in size. However, this is a hack. The Swift core team has shown interest on many occasions in adding support for the Equatable protocol to tuples, but at implementation time, no formal proposal has been discussed.

Ghost in the shell

As an informal type, Void cannot be extended. But Void is a type, so it can be used as a generic constraint.

For example, consider a generic container with the following single value:

struct Wrapper<Value> {
    let value: Value
}
Copy the code

When the type of the value wrapped by the generic container itself complies with the Equatable protocol, we can first extend the Wrapper to support the Equatable protocol using Swift 4.1’s killer feature condition compliance.

extension Wrapper: Equatable where Value: Equatable {
    static func= =(lhs: Wrapper<Value>, rhs: Wrapper<Value>) -> Bool {
        return lhs.value == rhs.value
    }
}
Copy the code

Using the same technique as before, we can implement a global == function that accepts the Wrapper

argument to achieve almost the same effect as the Equatable protocol.

func= =(lhs: Wrapper<Void>, rhs: Wrapper<Void>) -> Bool {
    return true
}
Copy the code

In this case, we can compare two wrappers with Void values.

Wrapper(value: void) == Wrapper(value: void) // true
Copy the code

However, when we try to assign such a wrapper value to a variable, the compiler generates a weird error.

let wrapperOfVoid = Wrapper<Void>(value: void)
// 👻 error: cannot assign:
// Could not destroy wrapperOfVoid because the symbol could not be found
Copy the code

The horror of Void in turn negates itself again.

The phantom type

Even if you’re afraid to mention its unofficial name, you can’t escape the grip of Void.

Any function that does not explicitly declare a return value implicitly returns Void.

func doSomething(a){... }/ / is equivalent to

func doSomething(a) -> Void{... }Copy the code

This behavior is strange, but not particularly useful. And when you assign the return value of a function that returns Void to a variable, the compiler generates a warning.

doSomething() // No warning

let result = doSomething()
// ⚠️ the constant 'result' points to a value of type 'Void'. The result of this behavior is unpredictable
Copy the code

You can explicitly specify the variable type Void to eliminate warnings.

let result: Void = doSomething() / / ()
Copy the code

Conversely, if a function returns a non-void value and you do not assign the return value to another variable, the compiler also raises a warning. For more details, see SE-0047 “Default Alarm When The Result returned by non-void function is Not used”.

Try to recover from Void

If you squint at Void? Long enough, you might confuse it with a Bool. Both types are similar in that they have only two states: true /.some(()) and false /.None.

But similar doesn’t mean the same. They are two of the most obvious difference is that Bool follow ExpressibleByBooleanLiteral agreement, and the Void is not cannot follow ExpressibleByBooleanLiteral agreement, For the same reason it doesn’t follow the Equatable protocol. So you can’t do this:

(true as Void?)./ / error
Copy the code

Void is probably the most creepy freak type in Swift. But when you give Bool a Booooooool alias, it’s just like Void.

But the Void? Hard col is able to behave like Bool. For example, the following function throws a random error:

struct Failure: Error {}

func failsRandomly(a) throws {
    if Bool.random() {
        throw Failure()}}Copy the code

Instead, call the function with a try expression in a do/catch block.

do {
    try failsRandomly()
    // Successfully executed
} catch {
    // Failed to execute
}
Copy the code

The fact that failsRandomly() implicitly returns Void can achieve the same effect, which is incorrect but seemingly feasible. try? The expression handles statements that might throw exceptions, wrapping the result as an optional type value. In the case of failsRandomly(), the result is Void? . If the Void? There are.some values (i.e.,! = nil), which means the function returns without error. If success is nil, then we know that the function generated an error.

let success: Void? = try? failsRandomly()
ifsuccess ! =nil {
    // Successfully executed
} else {
    // Failed to execute
}
Copy the code

A lot of people may not like do/catch blocks, but you have to admit that they are much more elegant than the code here.

In some special situations, this workaround can be useful. For example, to preserve the side effects of each self-evaluated closure execution, you could use static properties on a class:

static var oneTimeSideEffect: Void? = {
   return try? data.write(to: fileURL)
}()
Copy the code

While this works, it’s better to use Error and Bool.

Things that ring “Clang” at night

If you start to shiver when reading this chilling description, you can channel Void necrotic energy to summon great heat to your spirit:

In other words, make the LLdb-Rpc-server switch on the CPU at full speed with the following code:

extension Optional: ExpressibleByBooleanLiteral where Wrapped= =Void {
    public typealias BooleanLiteralType = Bool

    public init(booleanLiteral value: Bool) {
        if value {
            self.init(())!
        } else {
            self.init(nilLiteral: ())! }}}let pseudoBool: Void? = true // We'll never find out that it was caused here
Copy the code

In the tradition of Lovecraft horror novels, Void has a physical structure that a computer can’t process; We witnessed briefly how it can make a process hopelessly insane.

A triumph in disguise

Let’s end this magical learning journey with a familiar piece of code:

enum Result<Value.Error> {
    case success(Value)
    case failure(Error)}Copy the code

If you remember from our previous article on the Never type, you know that setting the Error type of Result to Never allows it to indicate certain operations that always succeed.

Similarly, an operation that succeeds but does not produce a meaningful result can be represented by Void as a Value.

For example, an application might implement a heartbeat by simply “ping” the server on a regular basis with a network request.

func ping(_ url: URL, completion: (Result<Void, Error>) -> Void) {
    // ...
}
Copy the code

According to HTTP semantics, the correct status code for a virtual /ping terminal should be 204 No Content.

In the requested callback, success is indicated by the following call:

completion(.success(()))
Copy the code

If you think there are too many parentheses (what’s wrong with them?) Add a key extension to Result to make things easier:

extension Result where Value= =Void {
    static var success: Result {
        return .success(())
    }
}
Copy the code

No pain, no gain.

completion(.success)
Copy the code


While this may seem like a purely theoretical or even abstract exercise, exploring Void gives us a deeper insight into the roots of the Swift programming language.

Long before Swift came along, tuples played an important role in programming languages. They can represent parameter lists and enumeration associations and play different roles depending on the scenario. But in some cases, the model breaks down. Programming languages still fail to reconcile the differences between these different constructs.

According to the Swift myth, Void would be the epitome of those old gods: it’s a true singleton, and you wouldn’t even notice what it does or what it does; The compiler also ignores it.

Perhaps all this is just a marginal invention of our understanding, a manifestation of our concern about the future of the language. In short, when you stare at Void, Void gazes back at you.

This article is translated by SwiftGG translation team and has been authorized to be translated by the authors. Please visit swift.gg for the latest articles.