[swift-evolution] [Idea] Repurpose Void

Brent Royal-Gordon brent at architechies.com
Sun Apr 24 20:43:48 CDT 2016


> Some people, including me, argue that Void should be removed altogether, because:
> 1) () is more consistent with curried functions: (Int) -> () -> Double
> 2) () follows functional programming traditions
> 
> Now, why don't we repurpose Void to follow functional programming traditions as well?
> Its definition will look like:
> enum Void { }
> 
> Basically, Void is a type which cannot have values.

I don't think Void is a good name for this, because it means something wildly different from the C-style `void`. It would be a confusing name choice for no real reason.

> With it, we can eliminate at least two Swift special cases:
> 
> 1. Noreturn functions
> 
> func exit(code: Int = 0) -> Void
> 
> From this signature, it's obvious that `exit` cannot return normally
> 
> 2. Rethrows
> 
> func call<T, U>(block: () throws T -> U) throws T -> U
> 
> Non-throwing functions are functions throwing Void.
> So if T=Void, we get the non-throwing version of `call`.

As for these, I think we would actually be better off having a bottom type for these use cases. A bottom type is a subtype of all types—sort of the opposite of `Any` (which is at the top of the type graph). Since it's impossible for any value to belong to all types simultaneously, it's impossible to construct a value of the bottom type, so it has the same role of meaning "this never returns/happens". But it has an important advantage over an empty enum type: you can treat it as any type you'd like.

For instance, suppose we spell the bottom type `_`. Then we can write, for instance, this function, which indicates we haven't finished writing something:

	func unimplemented(_ description: String, file: String = #file, line: String = #line) -> _ {
		fatalError("\(description) unimplemented", file: file, line: line)
	}

And use it like so:

	func calculateThing() -> Thing {
		if let cachedThing = cachedThing {
			return cachedThing
		}
		
		cachedThing = Thing(foo: calculateFoo(), bar: unimplemented("Calculation of bar"))
		return cachedThing!
	}

Because `unimplemented()` returns the bottom type, we can use it anywhere in any expression expecting any type, and it will compile just fine, implicitly converting to any type the context demands. Of course, since there are no values of the bottom type, `unimplemented()` cannot actually return a value, and so the call to `Thing.init(foo:bar:)` can never actually occur. But from the type checker's perspective, it all works out fine.

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list