Could we flip `Void`'s underlying type (one idea)?

This is a random thought and I'm not sure where the discussion will lead to. Semantically we can represent Void as Optional<Never> (or Never?).

Never? implies that we 'never return a value', where the returned value is .none which represents 'nothing' in the semantical context. Therefore () -> Never? is a function that returns nothing or in other words never returns a value. This is either what Void semantically means or overlaps it fairly well.

I wonder if we could flip Void like so:

typealias Void = Never?

As a bonus Void will automatically become Equatable and Hashable because Never conforms to those protocols and Optional conditionally conforms to them when Wrapped does.


Sure there are some strange implications such as:

  • return .none or return nil will become possible everywhere where we expect Void today
  • I'm not sure how to deal with the empty tuple literal () to keep source compatibility
  • The compiler might need to optimize memory layout size for Never? to 0 as it already does for enums with a single case.

Therefore this is a discussion so we can observe different opinions about this wild idea.

Ignoring source-compatibility / ABI concerns, I don't think this make sense to do semantically. Void has a purpose as the empty type. Never is the type you cannot make. Optional either has a value or it doesn't. Optional<Never> is like a contortion of existing types without any semantic backing behind it; it can never have a value.

Being able to return nil from a void function is a silly idea. It would destroy code readability; you'd have to scroll up to the method definition to double check if this is a function returning an actual Optional result or is it just a void function.

4 Likes

And that's exactly what Void semantically means, no?

Here I don't disagree with you. Today we can already write return (), but we don't because of convenient reasons, the same could apply to the 'potential ability of writing return .none' because of this change.

I'm not trying to hardly push this idea, but rather gather different view points on this. Thank you for sharing yours. :slight_smile:

Not quite IMO because Void has structural weight in that it can never be anything but one value. So one natural result of that is that you don't/can't write extensions on Void.

Whereas if everything void is now a typealias of Optional<None>, you start hanging all the methods of Optional onto every void function.

func thisIsAVoidFunction() -> Optional<Never> { ... }

// ... now you can write meaningless stuff like this and have it compile
thisIsAVoidFunction()?.flatMap { $0 } 
thisIsAVoidFunction()?.hashValue 
2 Likes

To me this is exactly the same as:

typealias Void = ()

// vs

typealias Void = enum { case none }

I personally don't think some of the implications of having typealias = Never? are that meaningless.

thisIsAVoidFunction() == thisIsAVoidFunction()
thisIsAVoidFunction().hashValue

Sure you can write other truly meaningless examples, but this is possible with other types as well. That said I'm not arguing for need of the ability to write meaningless code on Void, therefore I appreciate your feedback.

I really don't see much benefit to making this change. IMO if we could revisit Void altogether, I'd rather we just remove it and require users to use (). Besides that, it really doesn't make sense to have "returns void" return an optional anything.

Swift requires all functions to have one return type, which is part of the reason we don't have a way to express a 1-tuple in Swift. Something typed as (...) -> () is effectively saying that it returns the 0-tuple, which some call Void in Swift land. (...)-> Never? would be saying that it returns an optional, which doesn't make sense when you know you have nothing meaningful to return, since optional is meant to represent the possibility of something existing or not existing. Whereas the 0-tuple is, well, the 0-tuple. But it is something. Returning Never? is non-sensical since you can never return the some case, effectively making case nil represent the 0-tuple anyway.

1 Like

Never means that we never return, full stop - we don't return an optional, we don't return a value representing 'nothing' (that would be .none), we don't return no value at all (that would be Void) - so Never? doesn't really make any sense, imho, and basing the concept of 'returns but doesn't provide a value' from 'does not return' makes even less sense.

2 Likes

I don't understand your view point. In Swift we always return something, there is no such thing that we don't return anything. 'Returning nothing' means implicitly returning Void which is a single value that represents 'nothing' conceptually. Never? is the same as it only can return .none with the semantical meaning of 'nothing' just like Void does. So to me _: () -> Void = { return () } and _: () -> Never? { return .none } is ultimately the same thing.

Could you please correct me if I misunderstood you?

I disagree on the phrasing because it's not non-sensical or meaningless. T? is a tuple of two possible values, one which represents the value of T and the other represents the notion of nil or nothing through case none. And theoretically T? is a super-type of T.

Never? eliminates case some(Wrapped) entirely and reduces it to only an enum with a single case that still represents the nothing of nil or nothing through case none. Therefore Never? behaves like R and would be a subtype of R?. Since it behaves like a subtype of an optional with two values, it may deserve a standalone name where semantically it could be Void as it stands for 'nothing'.

Other than that I respect your view point and appreciate your honest feedback.

I don't like it. Even though you're right that Never? and Void are isomorphic, hold exactly the same amount of information, and only has the trivial mapping between the two, they still hold semantic value.

Although Result<Success, Failure> could be represented by an Either<Left, Right> we chose not to. Why? Because naming matters.

4 Likes

But I would like an enum Void { case void } and that it could get equatable and hashable conformance by default.

1 Like

I think you're looking at this the wrong way. The 0-tuple is the way to represent the void type in Swift. Void is just another name (i.e. a typealias) for the 0-tuple. Optional as a type represents something completely different from the void type.

Another way to define Void could have been another empty enum named Void. And the compiler could've just handled it internally. But the design of functions was based around taking and returning tuples. This was more prevalent in the days of tuple splatting and such.

1 Like

So you're saying Void could be an uninhabited type like Never, or would you rather have an empty struct as Void?

It would have to be a single-case enum. It doesn't hold any information, but all void-returning functions would have to return something. And all voids would be equal.

1 Like

I guess if you wanted to have less internal compiler magic vs an enum, struct Void would be ideal, since the size of a struct with no members is 0, but you can still create "instances" of it, so things like return Void() would still work.

1 Like

Unless the initialization is exposed through an empty tuple literal () while the main init would remain private. You could also make a struct Void into a singleton, but that's rather optimization.

Something like this:

public protocol ExpressibleByEmptyTupleLiteral {
  associatedtype EmptyTupleLiteral = // some stdlib private type
  init(emptyTupleLiteral tuple: EmptyTupleLiteral)
}

public struct Void: Hashable, RepresentableByEmptyTupleLiteral {
  private static let _shared = Void()
  private init() {}
  public init(emptyTupleLiteral tuple: EmptyTupleLiteral) {
    return _shared // factory init 
  }
}

Void is a type whose single value, (), has no functionality. An Optional is a container-like type which may be inhabited or not. It would be strange to say that a function which conceptually returns nothing (the value) can be mapped. And it would be strange for unconstrained extensions on Optional to work, at least syntactically, on the result of Void functions.

I think the bottom line is that we don't expect, nor desire, Void to behave like an Optional.

6 Likes
Terms of Service

Privacy Policy

Cookie Policy