user3441734
user3441734

Reputation: 17572

T, Optional<T> vs. Void, Optional<Void>

// this declaration / definition of variable is OK, as expected
var i = Optional<Int>.None
var j:Int?

// for the next line of code compiler produce a nice warning
// Variable 'v1' inferred to have type 'Optional<Void>' (aka 'Optional<()>'), which may be unexpected
var v1 = Optional<Void>.None

// but the next sentence doesn't produce any warning
var v2:Void?

// nonoptional version produce the warning 'the same way'
// Variable 'v3' inferred to have type '()', which may be unexpected
var v3 = Void()

// but the compiler feels fine with the next
var v4: Void = Void()

What is the difference? Why is Swift compiler always happy if the type is anything else than 'Void' ?

Upvotes: 1

Views: 292

Answers (2)

Rob Napier
Rob Napier

Reputation: 299485

The key word in the warning is "inferred." Swift doesn't like inferring Void because it's usually not what you meant. But if you explicitly ask for it (: Void) then that's fine, and how you would quiet the warning if it's what you mean.

It's important to recognize which types are inferred and which are explicit. To infer is to "deduce or conclude (information) from evidence and reasoning rather than from explicit statements." It is not a synonym for "guess" or "choose." If the type is ambiguous, then the compiler will generate an error. The type must always be well-defined. The question is whether it is explicitly defined, or defined via inference based on explicit information.

This statement has a type inference:

let x = Foo()

The type of Foo() is explicitly known, but the type of x is inferred based on the type of the entire expression (Foo). It's well defined and completely unambiguous, but it's inferred.

This statement has no type inference:

let x: Foo = Foo()

But also, there are no type inferences here:

var x: Foo? = nil
x = Foo()

The type of x (Foo?) in the second line is explicit because it was explicitly defined in the line above.

That's why some of your examples generate warnings (when there is a Void inference) and others do not (when there is only explicit use of Void). Why do we care about inferred Void? Because it can happen by accident very easily, and is almost never useful. For example:

func foo() {}
let x = foo()

This is legal Swift, but it generates an "inferred to have type '()'" warning. This is a very easy error to make. You'd like a warning at least if you try to assign the result of something that doesn't return a result.

So how is it possible that we assign the result of something that doesn't return a result? It's because every function returns a result. We just are allowed to omit that information if the return is Void. It's important to remember that Void does not mean "no type" or "nothing." It is just a typealias for (), which is a tuple of zero elements. It is just as valid a type as Int.

The full form of the above code is:

func foo() -> () { return () }
let x = foo()

This returns the same warning, because it's the same thing. We're allowed to drop the -> () and the return (), but they exist, and so we could assign () to x if we wanted to. But it's incredibly unlikely that we'd want to. We almost certainly made a mistake and the compiler warns us about that. If for some reason we want this behavior, that's fine. It's legal Swift. We just have to be explicit about the type rather than rely on type inference, and the warning will go away:

let x: Void = foo()

Swift is being very consistent in generating warnings in your examples, and you really do want those warnings. It's not arbitrary at all.


EDIT: You added different example:

var v = Optional<Void>()

This generates the error: ambiguous use of 'init()'. That's because the compiler isn't certain whether you mean Optional.init() which would be .None, or Optional.init(_ some: ()), which would be .Some(()). Ambiguous types are forbidden, so you get a hard error.

In Swift any value will implicitly convert with it's equivalent 1-tuple. For example, 1 and (1) are different types. The first is an Int and the second is a tuple containing an Int. But Swift will silently convert between these for you (this is why you sometimes see parentheses pop up in surprising places in error messages). So foo() and foo(()) are the same thing. In almost every possible case, that doesn't matter. But in this one case, where the type really is (), that matters and makes things ambiguous.

var i = Optional<Int>()

This unambiguously refers to Optional.init() and returns nil.

Upvotes: 2

Cristik
Cristik

Reputation: 32850

The compiler is warning you about the "dummy" type Void, which is actually an alias for an empty tuple (), and which doesn't have too many usages.

If you don't clearly specify that you want your variable to be of type Void, and let the compiler infer the type, it will warn you about this, as it might be you didn't wanted to do this in the first place.

For example:

func doSomething() -> Void {
}

let a = doSomething()

will give you a warning as anyway there's only one possible value doSomething() can return - an empty tuple.

On the other hand,

let a: Void = doSomething()

will not generate a warning as you explicitly tell the compiler that you want a Void variable.

Upvotes: 0

Related Questions