[Perl 6 page]

The Void Type

The Synopses only mention the Void type once, in S04 version 68, in passing: “…place an explicit return statement after it, or declare a return type of Void.” However, the void context (as opposed to item context or list context) is mentioned several times. So, we know we can declare a function’s return type to be Void, but what exactly does that mean? This page elaborates on that.

In the C programming language, the void type is not really a type, but syntactically is used where a type name is expected, to indicate that a function does not return any value at all. Such a faux type was introduced because leaving off any type there already meant that an int would be returned. Similarly in Perl, not declaring a return type at all means that anything can be returned, not nothing. However, in Perl, the Void type needs to be a real type, not just a syntactic marker that parses like a type name.

It is clear that the Void type has the same intent: A Void function does not return any interesting value. The compiler knows that nothing interesting will be returned at the point of call, and the function knows to discard anything in an implicit return.

There are several ways to construct a Void type that does what we want. But a fundamental choice is whether the normal value of Void is defined or undefined. Let’s examine the former case first, since it turns out to be a dead-end:

Suppose we say that the domain of the Void type has only one (immutable) value. All Void values are equivalent. However, what does a plain return do? With normal types, if you say return without specifying a value, it returns undef. So if you return from a Void function or just fall off the end, you are ignoring the singleton anyway and just returning undef. So a single-value Void becomes another kind of boolean value, which can be undef or not-undef. It does have the advantage of making it easy to detect the undefined Failure object if the function fails, but only if every return were coded as a special return Void-constant; instead of plain return. Or, the rules for returning could be changed just for this case, but that would make Void different from other types.

So, we conclude that Void has no legal (defined) values in its domain. This also has the advantage of eliminating any other decisions to be made concerning the type. It is a singular definition logically arrived at from the requirements.

Consider two functions, one with and one without a return type declaration:

use fatal;  # failures throw exceptions

sub f1 () 

sub f2 () returns Void

Basically, we expect both functions to behave in the same way. The Void return type just formalizes it, stating up front that we will not return an interesting value.

So what if the function fails? That is, instead of return it calls fail. Perhaps statement1 fails. Since there is a use fatal in lexical scope, the failure will throw an exception that will try to propagate out of f1 or f2. But if the caller of f1 or f2 did ask for failure returns rather than exceptions?

no fatal;   # failures return error as property on undef
my $err = f1;
if ($err ~~ Failure) { ... }

Now replace call to f1 with f2, and it should work the same way. The Void acts like any other type. Returning undef returns a typed undef, which is actually the undefined protoobject for that type. The fact that there are no normal values is not relevant to the issue. Just as for any other declared return type, failures are returned by mixing in the Failure with the typed undef object. So, even though you never expect an interesting value in the domain of Void, you can still declare variables of that type and look for interesting forms of undef. Here, $err was not given a type, but if we wanted to define a type for it, it would be Void.

So, assuming that the compiler knows that fail is set to throw in the current scope, should it call you a silly goose for not ignoring the return value? Well, the function might do something like this:

sub f3 () returns Void
 return undef but 5;

use fatal;
my Void $x = f3;

It also makes you wonder just what is “void context”? The want context is dynamic, and regardless of what type the function returns, it is in void context if the return value is ignored. That is no different with the Void return. Here, f3 could determine that it is called in item context. A function is in void context not because the return type is Void, but because the caller doesn’t use the returned value at all.