<p>Unit types are types found in more modern languages, replacing <code>void</code> in the C-family languages. Unlike <code>void</code>, a unit type has an actual value, exactly one. This allows functions that return nothing to be used in expressions and the like. Initially, my language had a <code>Void</code> type, which simply served the purpose of <code>void</code> in the C-likes (I thought a unit type would be too hard to implement for the time being). However, as my compiler grew, <code>Void</code> slowly gained more and more &quot;realness&quot; until I got to the point where it was just below a &quot;real&quot; type. All that was needed was a few more lines of code and it'd be a full-blown unit type. What are the advantages of me turning it into a unit type versus just having an equivalent of C's <code>void</code>?</p>


<types><unit-type>
What are the pros and cons of having a unit type over C-like void?
CC BY-SA 4.0
2023-07-10T03:54:10.607Z
I don't think the two are equivalent, in fact the equivalent of `void` should be `Unit | Never`
Aster
CC BY-SA 4.0
2023-07-10T07:18:04.293Z
The void type in C was a design error, retained for historical reasons. Don't emulate it. (It seems like all the actual answers support this.)
Glenn Willen
CC BY-SA 4.0
2023-07-10T16:14:58.450Z
@Aster well, then you could say the same about all types. A C function whose result is an `int` is also not guaranteed to terminate; yet it's generally understood that this has nothing to do with the `int` type having an extra value `Never`, but simply that it's always possible for a function to not yield a value at all in finite time. By contrast, Haskell does consider nontermination as a special (un-matchable) value `⊥` which every type has in addition to its "proper" values. In that sense Haskell's unit type `()` has indeed two values, `()` and `⊥`.
leftaroundabout
CC BY-SA 4.0
2023-07-11T20:03:21.263Z
And yet in Racket `(void)` evaluates to the singleton value `#<void>`
D. Ben Knoble
CC BY-SA 4.0
2023-07-11T21:25:38.610Z
" This allows functions that return nothing to be used in expressions and the like"  C++ allows this and its `void` is certainly "C-like"
Ben Voigt
CC BY-SA 4.0
2023-07-11T22:01:56.657Z
@BenVoigt but others with such a `void` dont
Seggan
CC BY-SA 4.0
2024-03-09T21:18:05.253Z
C's `void` is ultimately an idiosyncratic unit type. It's a unit type, but it's not actually first-class (for instance, you can't have a variable of type `void`). It's treated as a special case. IMO, there isn't much reason to have such a special case and it'd be nicer to have a first-class unit type. The name adds to the confusion, since "void" suggests it's an empty type but it isn't an empty type (it's just that there are restrictions to where you're allowed to have the value).
David Young
CC BY-SA 4.0
2024-12-27T14:36:46.617Z
Side note: <https://wg21.link/p0146>, if adopted, would have made `void` into (mostly) an ordinary unit type in C++. The paper also discusses how much grief the current irregularity of `void` causes.
user3840170
<p>I'd say there is no con to having a unit type. Even C++, while inheriting <code>void</code> from C has a unit type standardized in the form of <code>std::monostate</code>. Moreover, even with <code>void</code>, C and C++ allow constructs like:</p>
<pre class="lang-cpp prettyprint-override"><code>void f() {}

void g() { return f(); }
</code></pre>
<p>So really, I'd say void type is merely a slightly restricted version of unit type. If your language has some custom weird behaviours related to void (like forbidding its use in structures, perhaps), you may, in the worst case, extend them to the unit type too. Regardless, the users will probably appreciate the increased generality.</p>

CC BY-SA 4.0
2023-07-09T21:32:43.260Z
2023-07-10T14:38:45.263Z
2023-07-10T14:38:45.263Z
abel1502
CC BY-SA 4.0
2023-07-09T23:14:02.530Z
Though C++'s `void` gets hairy when you try to assign the result of a `void` function to a local variable temporarily. It's really easy to make a template function accidentally stop working when `T = void` by changing something trivial like that; trust me, I've been there.
Silvio Mayolo
<p>It could make sets a special case of maps. That is, <code>std::map&lt;T, void&gt;</code> works very like <code>std::set&lt;T&gt;</code>. In a theoretical new language we could make the way we use both of them identical.</p>
<p>I don't know a case that makes it obviously useful except for writing less code implementing both of them, though.</p>

CC BY-SA 4.0
2023-07-10T13:19:49.537Z
2023-07-10T14:38:14.583Z
2023-07-10T14:38:14.583Z
user23013
CC BY-SA 4.0
2023-07-10T16:09:48.687Z
That is an example of a unit type though. That it works in C++ is because C++ special-cases this particular use.
Longinus
CC BY-SA 4.0
2023-07-10T16:22:29.800Z
@Longinus I don't think it works in C++. At least it doesn't compile in g++. The point is if void was a unit type, this could easily be supported, as something in the pros list. (Also, the downvoter has reversed their vote after my edit.)
user23013
CC BY-SA 4.0
2023-07-10T16:51:17.640Z
Ah I see, I misunderstood what you wrote. _(Also why did I believe C++ allowed that? I don't know)_
Longinus
<h2>Generic callback arguments</h2>
<p>Consider a function with a generic type argument, such as the <code>resolve</code> callback to a TS <code>Promise</code>:</p>
<pre class="lang-typescript prettyprint-override"><code>let x = new Promise&lt;T&gt;((resolve, reject) =&gt; {
    // resolve has type (T) =&gt; void
});
</code></pre>
<p>If <code>void</code> is not usable in generic types, as in Java and C#, these have to be special-cased. And indeed they are: C# has different types <a href="https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task" rel="noreferrer"><code>Task</code></a> and <a href="https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1" rel="noreferrer"><code>Task&lt;TResult&gt;</code></a>. Having a first-class unit type makes library code like this simpler and less redundant.</p>
<p>On the other hand, if (as in C) an argument list of <code>(void)</code> means &quot;no arguments&quot;, this becomes easier on the call<em>er</em>. To call a function of type <code>(Void) -&gt; Void</code> in Swift requires an extra pair of parens, <code>foo(())</code>, which just serves as visual noise.</p>

CC BY-SA 4.0
2023-07-09T21:44:05.413Z
2023-07-09T21:44:05.413Z
Bbrk24
<p>Disadvantage: the language lets you do more silly things. Everywhere there isn't a value, you can treat it as a value and you get some nonsense like <code>unit</code> or <code>()</code>. For example, Haskell is happy to let you store <code>()</code> in a variable and print it.</p>
<p>All else being equal, a language that prevents more mistakes is a better language.</p>

CC BY-SA 4.0
2023-07-11T13:59:42.023Z
2023-07-11T13:59:42.023Z
Criticize SE actions means ban
CC BY-SA 4.0
2023-10-14T20:34:08.483Z
Languages that remove functionality because it's allegedly error prone are all good until it prevents someone from doing something because they think it's a mistake but in reality they know what they're doing.
CPlus
CC BY-SA 4.0
2023-10-17T12:01:30.013Z
@user16217248 Then I suppose C++ is the only language you use. **Every** other language has gone down the path of removing features to achieve the best power-to-weight ratio. C++ is the only language which focuses on adding many features to achieve the most raw power.
Criticize SE actions means ban
CC BY-SA 4.0
2023-10-17T20:59:43.310Z
@user253751 Well technically I use C more but yeah basically. I think C++ is great.
CPlus
CC BY-SA 4.0
2023-07-11T14:08:25.130Z
I don't think this is a disadvantage. Being able to generalise functions that expect to store something, so that they don't actually store anything useful, is a good feature
pxeger
CC BY-SA 4.0
2023-07-11T14:08:27.030Z
Why is it "silly" or "a mistake" to do so?
Karl Knechtel
CC BY-SA 4.0
2023-07-11T19:56:20.940Z
@KarlKnechtel What do you expect print(print(42)) to do? I think if you interview 100 programmers who don't already know your language or similar ones, they probably didn't say 42unit or 42()
Criticize SE actions means ban
CC BY-SA 4.0
2023-07-24T14:27:01.087Z
That still doesn't explain why it's "silly" or a "mistake"
Seggan
CC BY-SA 4.0
2023-07-24T19:10:06.650Z
@Seggan-OnStrike I shouldn't have to explain why print(print(42)) is "silly" or a "mistake" - it should be readily apparent!
Criticize SE actions means ban
<p>In some contexts, <code>void</code> means that the function might have a return value, but the caller isn't supposed to use it. Consider this in Typescript:</p>
<pre class="lang-typescript prettyprint-override"><code>function doSomethingAndThen(callback: () =&gt; void) {
    doSomething();
    callback();
}
</code></pre>
<p>In Typescript, it's legal to provide a callback which <em>does</em> return something, e.g. <code>() =&gt; someArray.pop()</code>, because <code>() =&gt; T</code> is a subtype of <code>() =&gt; void</code> for all <code>T</code>. This is convenient for the programmer, to not have to write the more verbose <code>() =&gt; { someArray.pop(); }</code>. It also allows things like  <code>doSomethingAndThen(somethingElse)</code>, where <code>somethingElse</code> is a named function which already exists but has a return type.</p>
<p>This would be unsound if <code>void</code> were supposed to be a unit type like <code>undefined</code> (that is, the unit type whose only member is the value <code>undefined</code>), because the expression <code>callback()</code> would have type <code>undefined</code> despite not necessarily having the value <code>undefined</code>.</p>
<p>So, when <code>void</code> appears in return position like <code>() =&gt; void</code>, it is more analogous to a top type like <code>unknown</code> than a unit type. On the other hand, it's more useful to annotate functions as returning <code>void</code> rather than <code>unknown</code>, since the former indicates that even if some value is returned, it is meaningless and shouldn't be used.</p>

CC BY-SA 4.0
2023-07-24T12:33:17.567Z
2023-07-24T12:33:17.567Z
kaya3
<p>In C, <code>void</code> is used in four and a half different ways.  A unit type is equally good for one, worse for a second, better for two more, and totally unsuitable for the last.</p>
<p>Some languages have a Top or Bottom type, both distinct from the unit type, which is a better fit for some of the ways C uses <code>void</code>.</p>
<h3>Side-Effect Functions</h3>
<p>Marking the return type of a side-effect function is completely equivalent to a unit type in other languages.  There are no advantages one way or the other; it’s just spelled differently.  An unusual variation is Haskell: since there is no such thing as a side-effect function that’s pure, a pure function returning a unit type would be completely useless in a functional language—but many Haskell functions return <code>io ()</code>, which actually represents an <em>operation evaluating to</em> <code>()</code> that can be run.</p>
<h3>Throwing Away Return Values</h3>
<p>The second distinct use for <code>void</code> in C is that the cast <code>(void)</code> explicitly declares that you are ignoring a return value.  This can be necessary to make all the branches of a <code>?</code> expression have the same type.</p>
<p>In other contexts, C spells this cast <code>;</code>. This is partly because K&amp;R-style functions needed to return something, so a function that would return <code>void</code> in ANSI C returned <code>int</code> in K&amp;R C, and the programmer would just ignore the meaningless return value.  You will sometimes. however, see C programmers write it out as <code>(void)printf(&quot;hello, world!&quot;);</code>.  In fact, <code>lint</code> used to require it.  If your function is returning something so useless that it’s better to ignore it than to check it, that’s a code smell, but Dennis Ritchie would have copped to that.</p>
<p>The reason for this is that many C functions report errors in their return values, and the language makes it easy to shoot yourself in the foot by ignoring errors.  If programmers are forced to <em>explicitly</em> write out that they are ignoring the return value, they at least need to think about it, and are less likely to neglect to check for an error just because the language makes it easier to ignore them.</p>
<p>A modern strongly-typed language returns different types on success and error, like <code>Just x</code>/<code>Nothing</code> in Haskell, or <code>Ok ()</code>/<code>Err _</code> in Rust.  In practice, an option unit type is equivalent to a boolean, but might appear as a stage of a railway-oriented computation terminating in <code>()</code>.  A unit type is useful to compose a result type that reports either the fact of success or details about the error.  The <code>void</code> type in C or C++ cannot be used this way: a <code>std::variant</code> or <code>union</code> cannot contain <code>void</code>.</p>
<p>The language might or might not make it syntactically possible to ignore errors at all.  One solution would be to implicitly unwrap values, so that if (in Rust syntax) <code>foo</code> returns a <code>Result&lt;(),Error&gt;</code>, <code>foo(bar);</code> would implicitly check the return value.  If it’s <code>Ok ()</code>, the program proceeds, and if it’s an error, the program panics.  A language that did this would probably have a lot of unexpected runtime panics, but at least there would be a message giving the line of code where the error appeared and a useful stack trace.  Rust instead takes a middle ground (based on Swift) where unwrapping must be explicit, but there’s the syntax sugar <code>?</code> for unwrapping on success and short-circuiting on error.</p>
<p>Some languages use a return value of the bottom type (the type that no other is a subtype of) to denote the return value of an expression that never returns at all, such as <code>throw</code>.  This is very different from a function that always returns successfully!  A special case is a panic function that needs to be able to appear in any context.  In many languages, these functions that terminate the program are the one exception to the type system, since there should be no type-checking of its return value (or it can be thought of as having a fully-generic return type).  C has syntax to make all branches of a <code>?</code> expression return <code>(void)</code>, but not to ignore the type only of branches that do not return, and unify the type of all the ones that could.</p>
<h3>A Generic Reference</h3>
<p>C uses <code>void*</code> as the type of a generic object pointer.  A unit type would be a poor fit for this.  A top type from computer science (like <code>Object</code> or <code>Any</code>) would be more appropriate, if a language has such a thing at all.</p>
<p>C needed a type like <code>void*</code> because of some quirks required for compatibility.  A new language would not have the same requirements.  One is that K&amp;R-style functions don’t type-check their arguments, so it was common to write code like <code>memcpy(&amp;dest, &amp;src, sizeof(dest));</code>.  When C++ and ANSI wanted to add type-checking, they therefore needed a type that any other object pointer implicitly converts to.  Another quirk is that C historically had to support mainframes that used different pointer formats for character and word pointers, or even had pointer types of different sizes.  K&amp;R historically reads these out as <code>char*</code>, and mainframes with different word and character pointers historically made character pointers long enough to hold word pointers but not vice versa. Standard C and C++ therefore specified that it must be safe to convert any object type to either <code>void*</code> or <code>char*</code>. and furthermore that a function call with K&amp;R-style promotions must use the same representation for a conversion to <code>void*</code> or <code>char*</code>.  Finally, C keeps backward-compatibility with code like <code>char* foo; foo = malloc(SIZE);</code>, and in fact now lets you write <code>T* const foo = calloc(elems, sizeof(T));</code>, but this was a bridge too far for C++, which wanted programmers to stop using C-style memory management anyway.  (C++ then introduced a new unit type, <code>nullptr_t</code>, to restore this implicit conversion to, and not just from, any pointer type.  There had previously been a widely-used implementation by Scott Myers, in <em>Effective C++</em>.)</p>
<p>One disadvantage of a reference to a unit type is that the compiler will catch the logic error of dereferencing a <code>void*</code>.  If you tried to represent something similar using a reference to a unit type, in a language with automatic type inference, it would dereference to a valid object.</p>
<p>Another is that a generic object pointer must be at least as wide as any object pointer, plus whatever bits are needed to distinguish between different pointer types. It would me more efficient for all unit-type references to have zero bits of storage and compare equal to each other.</p>
<p>If you allow objects or references to the unit type to exist, there would also be no way to distinguish between their type and the fully-generic pointer type.</p>
<p>For most purposes that you would use a <code>void*</code> that isn’t <code>NULL</code>, it would be better to use the concept of a top type, one that every other type is a subtype of.  That would represent a reference that could be to any type, in a more rigorous way.  A weakly-typed language (one that only distinguishes between returning nothing or returning something, or one that has everything return some possibly-empty string) implicitly has a top type that it uses to represent some arbitrary untyped thing.  It might or might not have a name in the language itself.</p>
<h3>Empty Argument Lists</h3>
<p>The only reason this is a homonym for the unit type in C was that the more-intuitive syntax <code>f()</code>, as in most other C-family languages, already meant something different.  C does not use <code>void</code> to define an empty <code>struct</code>, but some C++ templates do.</p>
<p>Some other answers discuss whether, in a language with template or generic type parameters, <code>void</code> should be a valid type.  This essentially means that everything you specify as “of one generic parameter” really means “of zero or one parameter,” and everything you specify as <em>N</em> generic parameters really means “of up to <em>N</em> parameters.”  A language with generics, but not overloading, does need some construct that means, “any number of items,” “zero or one item,” and “exactly one item.”  These might be type qualifiers.</p>
<p>There are several advantages to representing a generic set of function parameters as a tuple, with its syntactic sugar, rather than with some other syntax like a C++ <code>T...</code> parameter pack.  In terms of semantics, and not just spelling, a unit type has the advantage that the logic of many operations that would fail for <code>void</code> work for <code>()</code>.  For example, you can call a function with the members of <code>()</code> as its arguments, or iterate over the members of <code>()</code>, but not pass in the value of a <code>void</code>.</p>
<p>It is possible to write around the quirks of <code>void</code> in C++, for instance by specializing the template, using <code>if constexpr (std::same_as&lt;T,void&gt;)</code> to handle the special case, or by defining a <code>concept notvoid</code> that lets you write <code>template &lt;notvoid T&gt;</code>.  In a language like Rust or Haskell, where generic code needs to specify at least one typeclass to be able to do anything with a generic parameter, <code>void</code> would be excluded as a possible type naturally.</p>
<h3>The Type of a Null Pointer (Possibly)</h3>
<p>C has never said that the type of <code>NULL</code> must be <code>void*</code>—and some compilers resolve it to a magic keyword instead.  But <code>((void*)0)</code> is the most common definition in C (not C++).</p>
<p>First, modern languages usually don’t have null pointers at all.  C.A.R. Hoare, who invented the concept, called it his “billion-dollar mistake.”  It’s a lot more than that today.  Modern languages typically give success and error values different types, so that attempting to use an error code as a reference becomes a type error.</p>
<p>A possibly-invalid reference should be an option type.  A constant representing an invalid machine address should be a pointer-sized integer.  A valid reference to no valid object might be a reference to the bottom type.  You shouldn’t attempt to shoehorn a reference to a unit type into this role just because C does something similar, any more than you should assume that <em>in</em> and <em>on</em> are the same in English because some other languages uses the same word for both, or that some other language has one word for <em>you</em>.</p>

CC BY-SA 4.0
2023-07-10T01:06:15.617Z
2023-07-11T14:19:10.770Z
2023-07-11T14:19:10.770Z
Davislor
CC BY-SA 4.0
2024-03-09T22:00:44.630Z
There is another option for the type of a null pointer. Let's say pointers in our language are represented as a parametrized type named `Ptr` which we can use like `Ptr<Int>`. Then, we can give `null` the type `forall A. Ptr<A>`. Of course, whether null pointers should exist at all is a separate question.
David Young
CC BY-SA 4.0
2024-03-09T23:07:57.383Z
@DavidYoung Isn’t that a reference to the top type? I’d normally expect the type checker to allow a value of that type to be used as any kind of pointer, which I would not want.
Davislor
CC BY-SA 4.0
2024-03-09T23:16:33.927Z
@DavidYoung I do suggest that as a solution for a generic reference. I would recommend against it as the type of an error code.
Davislor
CC BY-SA 4.0
2024-03-09T23:44:28.973Z
@Davislor What I mean is if you want the kind of setup like C where `null` can be used for any pointer type. Also top types are not necessarily what I mean here. I am largely talking about a system without subtyping (in the usual sense anyway). I'm not sure what you mean by an error code. `null` does not necessarily indicate an error. In fact, `null` is really the *only* value that (potentially) makes sense to have with that type since such a value would need to be able to be used at any pointer type (assuming you aren't allowed to cast a integer literal to a pointer or something like that).
David Young
CC BY-SA 4.0
2024-05-30T06:10:25.110Z
Follow-up: C23 adds `nullptr_t` (from C++) as a replacement for the type of a bull pointer. This was motivated primarily by implementations that `#define NULL 0`, but where a `void*` and the `int` value `0` have different widths. This breaks `NULL` in any context where the compiler cannot deduce that the type is `void*` and not `int`, such as passing it to a variadic function. Apparently it’s impossible to get everyone to stop defining `NULL` as `0`.
Davislor
CC BY-SA 4.0
2023-07-10T16:13:41.210Z
C++11 (I think) replaced the `NULL` macro with a `nullptr` keyword, with the type `nullptr_t`, which is convertible to, but distinct from, `void*`.
dan04
CC BY-SA 4.0
2023-07-10T16:34:30.987Z
@dan04 Even before that, GCC 4.9.2 [had introduced `__null` as a magic keyword in G++,](https://gcc.gnu.org/onlinedocs/gcc-4.9.2/libstdc++/manual/manual/support.html#std.support.types.null) and Scott Meyers had written a widely-cited `nullptr_t` template in his book *Effective C++*. However, I see that GCC on many platforms does still define `NULL` as `((void*)0)`.
Davislor
CC BY-SA 4.0
2023-07-11T08:39:00.793Z
Nullability, optionality, whatever you name it. As long as it is not the default and universally applicable (including stackable), it's fine.
Deduplicator
CC BY-SA 4.0
2023-07-11T09:30:54.610Z
Tiny nitpick, while Hoare did invent nulls in the '60s, he didn't *say that line* [until 2009](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/)
AakashM
CC BY-SA 4.0
2023-07-11T14:21:56.350Z
@AakashM While I liked the joke, I don’t want to spread any misinformation. So, removed.
Davislor
<h3>Type Theory</h3>
<p>It's never a bad idea to start with some theory. In programming language theory, a type is akin to the set of values that instances of the type can take.</p>
<p>Some types are a finite set: a boolean has 2 values, a 32-bits integer a little over 4 billions. Others are an infinite set: strings, lists, etc...</p>
<p>Empty and Unit are very different there:</p>
<ul>
<li>Empty: no value, the empty set.</li>
<li>Unit: one value, a singleton set.</li>
</ul>
<h3>Unit types and Empty types in the wild</h3>
<p>First of all, unit types <em>just exist</em>. Even in C!</p>
<p>A <code>struct</code> with no field, or similarly a tuple or any other <em>product type</em> with no field, is a unit type. An array of 0 elements is a unit type. You can create a value, and all values are equivalent.</p>
<p>Similarly, empty types <em>just exist</em>. Even in C!</p>
<p>A <code>union</code> with no field, or any other <em>sum type</em> with no alternative, is an empty type. You cannot create a value.</p>
<p>Unless you go out of your way to forbid them in a language, they emerge spontaneously.</p>
<h3>No value?</h3>
<p>This is correct, an Empty type cannot be instantiated.</p>
<p>This has several implications:</p>
<ul>
<li>A function taking an Empty type as argument can never be called.</li>
<li>A function returning an Empty type... actually will never return.</li>
<li>A generic sum type with an Empty type in some alternative will never have an instance of that alternative.</li>
</ul>
<p>Empty types are very useful markers, allowing to cull away execution paths that cannot exist. This is useful for communicating intent to other humans and tools alike.</p>
<p>For example, let's have a look at the definition of <code>abort</code> in C++ and Rust:</p>
<pre><code>[[noreturn]] void abort();
</code></pre>
<pre class="lang-rust prettyprint-override"><code>fn abort() -&gt; !;
</code></pre>
<p>C++ requires a dedicated attribute, which is only usable in return position, and doesn't mesh well with meta-programming.</p>
<p>Rust, on the other hand, treats <code>!</code> as just another type<sup>1</sup>. It can be used everywhere a type is expected, it's accessible via meta-programming, etc...</p>
<p><sup>1</sup> <em>Ahem, well, it's not quite just another type yet.</em></p>
<h3>What of C's <code>void</code>?</h3>
<p>C's <code>void</code> is a strange beast, behaving at times like a unit type and at times like an empty type.</p>
<p>When denoting the parameters or return type of a function, it doesn't denote a function that cannot be called, or will never return, so is somewhat like a unit type -- except there is not really any value.</p>
<p>Still, it's a bit weird:</p>
<pre class="lang-c prettyprint-override"><code>void foo(int);    //  One argument.
void bar(void);   //  No argument, of course!
</code></pre>
<p>Yet, no field of type <code>void</code> may exist in a <code>struct</code> or <code>union</code>, and no variable of type <code>void</code> may be declared. The latter, notably, means that direct chaining of return values is possible, but attempting to do so in two steps is not:</p>
<pre class="lang-c prettyprint-override"><code>void foo();

void ok() {
    return foo();
}

void error() {
    void v = foo();
    return v;
}
</code></pre>
<p>Code generation, whether with external tools or macros, and in C++ templates, particularly suffer from this &quot;special case&quot;.</p>
<h3>Uniformity, Regularity</h3>
<p>It's easier to shun C's <code>void</code>, and properly use unit and empty types instead.</p>
<p>It's easier both for the compiler writer and for the user, because it eliminates special cases!</p>
<p>From the user end, more specifically:</p>
<ul>
<li>The less &quot;special cases&quot;, the easier it is to learn a language.</li>
<li>The less &quot;special cases&quot;, the more &quot;brain cells&quot; available for actual problem solving.</li>
<li>The less &quot;special cases&quot;, the less issues in generic code.</li>
</ul>
<p>The only cons, really, is unfamiliarity for people who have no been exposed to the concept previously... and they'll be converted quickly once they realize how much it simplifies their life.</p>
<h3>Regrets</h3>
<p>I remember reading that Anders Hejlsberg, the creator of C#, infamously regretted the irregularity of <code>void</code> in C# due the difficulties this introduced in generic code.</p>
<p>I cannot find a citation, but I can point at TypeScript -- his next brainchild -- where he kept <code>void</code>, but made it more regular instead. You can have variables of type <code>void</code> in TypeScript, though they can only be of value <code>undefined</code>.</p>
<p>Still named <code>void</code>, but not quite like C's <code>void</code>.</p>

CC BY-SA 4.0
2023-07-10T17:21:35.517Z
2023-07-12T06:55:21.733Z
2023-07-12T06:55:21.733Z
Matthieu M.
CC BY-SA 4.0
2023-07-11T02:11:21.687Z
"A function returning a Void type... actually will never return." Why couldn't it just be unusable in expressions, the way C's actual `void` actually works? Why does the lack of a return value prevent `return`ing, if the code is not permitted to *use* the missing return value?
Karl Knechtel
CC BY-SA 4.0
2023-07-11T06:46:03.023Z
@KarlKnechtel: To return a value, you must create it. A value of a Void type cannot be created, hence you cannot call return with it.
Matthieu M.
CC BY-SA 4.0
2023-07-11T06:52:24.873Z
@Seggan-OnStrike: Your question asks about unit vs c-void. This answer explains what unit types and empty types are, which should make it evident that c-void is a weird mix of both, sometimes behaving like a unit type (you can return it) sometimes like an empty type (you can't create a value of it, nor a variable with its type). If it's not as clear as I hoped, let me know what you're missing and I'll try to clarify it.
Matthieu M.
CC BY-SA 4.0
2023-07-11T14:07:44.840Z
@MatthieuM. That isn't the question. Why does returning from a function entail returning *a value*?
Karl Knechtel
CC BY-SA 4.0
2023-07-11T14:11:51.787Z
@KarlKnechtel: It doesn't entail it, but it creates a corner case -- a difference between functions that return values vs functions that don't -- which makes the life of users more complicated in "generic" code, be it code generators, macros, templates, generics, ...
Matthieu M.
CC BY-SA 4.0
2023-07-12T11:32:13.877Z
"An array of 0 elements is a unit type. You can create a value, and all values are equivalent". Both for the empty struct and an array with 0 elements, this claim is not true in C. `malloc(0) != malloc(0)` is perfectly acceptable in the standard (the behavior is implementation defined), same goes for two statically allocated arrays of size 0. This violates the usual definition of a unit type.
Voo
CC BY-SA 4.0
2023-07-12T12:05:44.997Z
@Voo: `malloc` does NOT create an instance of a unit type, it allocates (raw) memory.
Matthieu M.
CC BY-SA 4.0
2023-07-12T18:05:19.717Z
@Matthieu I don't think C uses the terminology of "instance of a type" in the standard, so it's probably open to interpretation what that'd be. But in any case even for the alternative `int x[0]; int y[0]` case those won't compare equal either.
Voo
CC BY-SA 4.0
2023-07-13T07:31:11.370Z
@Voo: They do, as in `memcmp(x, y, sizeof(x)) == 0` (https://godbolt.org/z/jWebnWra9). As for "instance of a type", note that this answer starts with _programming language theory_, talking about programming language _in general_. Indeed, the section you talk about mentions _tuples_ and there's none in C.
Matthieu M.
CC BY-SA 4.0
2023-07-13T09:13:05.597Z
@Matthieu The part that I cite is about the claim that _C_ has a unit type, so clearly it's about C. But yes if you define equivalence in C in terms of memcmp then that should work. But then that also works for malloc(). Although usually equivalence classes are defined in terms of the relational operators - otherwise, every language can have a unit type.
Voo
CC BY-SA 4.0
2023-07-13T09:19:00.730Z
But I'll freely admit that I was in a nitpicking mood, mostly because the missing Unit Type in C# just caused me troubles the last few days again.
Voo
<p>An advantage of having a <code>unit</code> type instead of a <code>void</code> type is: <code>unit</code> is a regular type that functions normally, whereas <code>void</code> is weird and creates special cases.</p>
<p>Consider C#: they have <code>void</code> instead of <code>unit</code>, and when you want to write a wrapper for a function that returns a value, you have to write it twice:</p>
<pre class="lang-csharp prettyprint-override"><code>    // normal function
    T Wrap&lt;T&gt;(Func&lt;T&gt; f)
    {
        Console.WriteLine(&quot;Calling the wrapped function...&quot;);
        return f();
    }
    // special case to handle void
    void Wrap(Action f)
    {
        Console.WriteLine(&quot;Calling the wrapped function...&quot;);
        f();
    }
</code></pre>
<p>If C# had chosen <code>unit</code> instead of <code>void</code>, you would simply call <code>Wrap&lt;Unit&gt;()</code> with a <code>Func&lt;Unit&gt;</code>. <code>void</code> and also <code>Action</code> would become unneeded.</p>

CC BY-SA 4.0
2023-07-09T21:47:54.177Z
2023-07-09T22:01:26.640Z
2023-07-09T22:01:26.640Z
Eldritch Conundrum
CC BY-SA 4.0
2023-07-09T21:51:45.517Z
Is this a duplicate of my answer?
Bbrk24
CC BY-SA 4.0
2023-07-09T21:55:17.327Z
They both kinda make the same point, yes. I guess you win because you posted 3 minutes before me. But I think my example is clearer.
Eldritch Conundrum
CC BY-SA 4.0
2023-07-10T07:53:32.823Z
As Java successfully used `Void` in its generics I'm surprised that C# made it worse.
OrangeDog
CC BY-SA 4.0
2023-07-11T03:47:23.523Z
I believe in C++ it's legal to write `return f();` if `f()` is template-dependent and is of type `void` when the templates are filled in.
Criticize SE actions means ban
CC BY-SA 4.0
2023-07-11T04:53:20.073Z
It doesn't need to involve templates. `return f();` where `f()` returns `void` is perfectly legal C++. The same holds for C. I am not sure why C# decided to make it more cumbersome in this aspect. See the answer by @abel1502 for details.
Weijun Zhou
# What are the pros and cons of having a unit type over C-like void?
Posted by: username_0 on 2023-07-09
Unit types are types found in more modern languages, replacing `void` in the C-family languages. Unlike `void`, a unit type has an actual value, exactly one. This allows functions that return nothing to be used in expressions and the like. Initially, my language had a `Void` type, which simply served the purpose of `void` in the C-likes (I thought a unit type would be too hard to implement for the time being). However, as my compiler grew, `Void` slowly gained more and more "realness" until I got to the point where it was just below a "real" type. All that was needed was a few more lines of code and it'd be a full-blown unit type. What are the advantages of me turning it into a unit type versus just having an equivalent of C's `void`?
**Tags:** types unit-type
> **Comments:**
> **username_1**: I don't think the two are equivalent, in fact the equivalent of `void` should be `Unit | Never`  
> **username_2**: The void type in C was a design error, retained for historical reasons. Don't emulate it. (It seems like all the actual answers support this.)  
> **username_3**: @Aster well, then you could say the same about all types. A C function whose result is an `int` is also not guaranteed to terminate; yet it's generally understood that this has nothing to do with the `int` type having an extra value `Never`, but simply that it's always possible for a function to not yield a value at all in finite time. By contrast, Haskell does consider nontermination as a special (un-matchable) value `⊥` which every type has in addition to its "proper" values. In that sense Haskell's unit type `()` has indeed two values, `()` and `⊥`.  
> **username_4**: And yet in Racket `(void)` evaluates to the singleton value `#<void>`  
> **username_5**: " This allows functions that return nothing to be used in expressions and the like"  C++ allows this and its `void` is certainly "C-like"  
> **username_0**: @BenVoigt but others with such a `void` dont  
> **username_6**: C's `void` is ultimately an idiosyncratic unit type. It's a unit type, but it's not actually first-class (for instance, you can't have a variable of type `void`). It's treated as a special case. IMO, there isn't much reason to have such a special case and it'd be nicer to have a first-class unit type. The name adds to the confusion, since "void" suggests it's an empty type but it isn't an empty type (it's just that there are restrictions to where you're allowed to have the value).  
> **username_7**: Side note: <https://wg21.link/p0146>, if adopted, would have made `void` into (mostly) an ordinary unit type in C++. The paper also discusses how much grief the current irregularity of `void` causes.  
---
## 8 answers
### Answer by username_18 on 2023-07-10. Score: 31 | Accepted answer
In C, `void` is used in four and a half different ways. A unit type is equally good for one, worse for a second, better for two more, and totally unsuitable for the last.

Some languages have a Top or Bottom type, both distinct from the unit type, which is a better fit for some of the ways C uses `void`.

### Side-Effect Functions

Marking the return type of a side-effect function is completely equivalent to a unit type in other languages. There are no advantages one way or the other; it’s just spelled differently. An unusual variation is Haskell: since there is no such thing as a side-effect function that’s pure, a pure function returning a unit type would be completely useless in a functional language—but many Haskell functions return `io ()`, which actually represents an *operation evaluating to* `()` that can be run.

### Throwing Away Return Values

The second distinct use for `void` in C is that the cast `(void)` explicitly declares that you are ignoring a return value. This can be necessary to make all the branches of a `?` expression have the same type.

In other contexts, C spells this cast `;`. This is partly because K&R-style functions needed to return something, so a function that would return `void` in ANSI C returned `int` in K&R C, and the programmer would just ignore the meaningless return value. You will sometimes. however, see C programmers write it out as `(void)printf("hello, world!");`. In fact, `lint` used to require it. If your function is returning something so useless that it’s better to ignore it than to check it, that’s a code smell, but Dennis Ritchie would have copped to that.

The reason for this is that many C functions report errors in their return values, and the language makes it easy to shoot yourself in the foot by ignoring errors. If programmers are forced to *explicitly* write out that they are ignoring the return value, they at least need to think about it, and are less likely to neglect to check for an error just because the language makes it easier to ignore them.

A modern strongly-typed language returns different types on success and error, like `Just x`/`Nothing` in Haskell, or `Ok ()`/`Err _` in Rust. In practice, an option unit type is equivalent to a boolean, but might appear as a stage of a railway-oriented computation terminating in `()`. A unit type is useful to compose a result type that reports either the fact of success or details about the error. The `void` type in C or C++ cannot be used this way: a `std::variant` or `union` cannot contain `void`.

The language might or might not make it syntactically possible to ignore errors at all. One solution would be to implicitly unwrap values, so that if (in Rust syntax) `foo` returns a `Result<(),Error>`, `foo(bar);` would implicitly check the return value. If it’s `Ok ()`, the program proceeds, and if it’s an error, the program panics. A language that did this would probably have a lot of unexpected runtime panics, but at least there would be a message giving the line of code where the error appeared and a useful stack trace. Rust instead takes a middle ground (based on Swift) where unwrapping must be explicit, but there’s the syntax sugar `?` for unwrapping on success and short-circuiting on error.

Some languages use a return value of the bottom type (the type that no other is a subtype of) to denote the return value of an expression that never returns at all, such as `throw`. This is very different from a function that always returns successfully! A special case is a panic function that needs to be able to appear in any context. In many languages, these functions that terminate the program are the one exception to the type system, since there should be no type-checking of its return value (or it can be thought of as having a fully-generic return type). C has syntax to make all branches of a `?` expression return `(void)`, but not to ignore the type only of branches that do not return, and unify the type of all the ones that could.

### A Generic Reference

C uses `void*` as the type of a generic object pointer. A unit type would be a poor fit for this. A top type from computer science (like `Object` or `Any`) would be more appropriate, if a language has such a thing at all.

C needed a type like `void*` because of some quirks required for compatibility. A new language would not have the same requirements. One is that K&R-style functions don’t type-check their arguments, so it was common to write code like `memcpy(&dest, &src, sizeof(dest));`. When C++ and ANSI wanted to add type-checking, they therefore needed a type that any other object pointer implicitly converts to. Another quirk is that C historically had to support mainframes that used different pointer formats for character and word pointers, or even had pointer types of different sizes. K&R historically reads these out as `char*`, and mainframes with different word and character pointers historically made character pointers long enough to hold word pointers but not vice versa. Standard C and C++ therefore specified that it must be safe to convert any object type to either `void*` or `char*`. and furthermore that a function call with K&R-style promotions must use the same representation for a conversion to `void*` or `char*`. Finally, C keeps backward-compatibility with code like `char* foo; foo = malloc(SIZE);`, and in fact now lets you write `T* const foo = calloc(elems, sizeof(T));`, but this was a bridge too far for C++, which wanted programmers to stop using C-style memory management anyway. (C++ then introduced a new unit type, `nullptr_t`, to restore this implicit conversion to, and not just from, any pointer type. There had previously been a widely-used implementation by Scott Myers, in *Effective C++*.)

One disadvantage of a reference to a unit type is that the compiler will catch the logic error of dereferencing a `void*`. If you tried to represent something similar using a reference to a unit type, in a language with automatic type inference, it would dereference to a valid object.

Another is that a generic object pointer must be at least as wide as any object pointer, plus whatever bits are needed to distinguish between different pointer types. It would me more efficient for all unit-type references to have zero bits of storage and compare equal to each other.

If you allow objects or references to the unit type to exist, there would also be no way to distinguish between their type and the fully-generic pointer type.

For most purposes that you would use a `void*` that isn’t `NULL`, it would be better to use the concept of a top type, one that every other type is a subtype of. That would represent a reference that could be to any type, in a more rigorous way. A weakly-typed language (one that only distinguishes between returning nothing or returning something, or one that has everything return some possibly-empty string) implicitly has a top type that it uses to represent some arbitrary untyped thing. It might or might not have a name in the language itself.

### Empty Argument Lists

The only reason this is a homonym for the unit type in C was that the more-intuitive syntax `f()`, as in most other C-family languages, already meant something different. C does not use `void` to define an empty `struct`, but some C++ templates do.

Some other answers discuss whether, in a language with template or generic type parameters, `void` should be a valid type. This essentially means that everything you specify as “of one generic parameter” really means “of zero or one parameter,” and everything you specify as *N* generic parameters really means “of up to *N* parameters.” A language with generics, but not overloading, does need some construct that means, “any number of items,” “zero or one item,” and “exactly one item.” These might be type qualifiers.

There are several advantages to representing a generic set of function parameters as a tuple, with its syntactic sugar, rather than with some other syntax like a C++ `T...` parameter pack. In terms of semantics, and not just spelling, a unit type has the advantage that the logic of many operations that would fail for `void` work for `()`. For example, you can call a function with the members of `()` as its arguments, or iterate over the members of `()`, but not pass in the value of a `void`.

It is possible to write around the quirks of `void` in C++, for instance by specializing the template, using `if constexpr (std::same_as)` to handle the special case, or by defining a `concept notvoid` that lets you write `template` . In a language like Rust or Haskell, where generic code needs to specify at least one typeclass to be able to do anything with a generic parameter, `void` would be excluded as a possible type naturally.

### The Type of a Null Pointer (Possibly)

C has never said that the type of `NULL` must be `void*`—and some compilers resolve it to a magic keyword instead. But `((void*)0)` is the most common definition in C (not C++).

First, modern languages usually don’t have null pointers at all. C.A.R. Hoare, who invented the concept, called it his “billion-dollar mistake.” It’s a lot more than that today. Modern languages typically give success and error values different types, so that attempting to use an error code as a reference becomes a type error.

A possibly-invalid reference should be an option type. A constant representing an invalid machine address should be a pointer-sized integer. A valid reference to no valid object might be a reference to the bottom type. You shouldn’t attempt to shoehorn a reference to a unit type into this role just because C does something similar, any more than you should assume that *in* and *on* are the same in English because some other languages uses the same word for both, or that some other language has one word for *you*.
> **Comments:**
> **username_19** (2023-07-10): C++11 (I think) replaced the `NULL` macro with a `nullptr` keyword, with the type `nullptr_t`, which is convertible to, but distinct from, `void*`.  
> **username_18** (2023-07-10): @dan04 Even before that, GCC 4.9.2 [had introduced `__null` as a magic keyword in G++,](https://gcc.gnu.org/onlinedocs/gcc-4.9.2/libstdc++/manual/manual/support.html#std.support.types.null) and Scott Meyers had written a widely-cited `nullptr_t` template in his book *Effective C++*. However, I see that GCC on many platforms does still define `NULL` as `((void*)0)`.  
> **username_20** (2023-07-11): Nullability, optionality, whatever you name it. As long as it is not the default and universally applicable (including stackable), it's fine.  
> **username_21** (2023-07-11): Tiny nitpick, while Hoare did invent nulls in the '60s, he didn't *say that line* [until 2009](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/)  
> **username_18** (2023-07-11): @AakashM While I liked the joke, I don’t want to spread any misinformation. So, removed.  
> **username_6** (2024-03-09): There is another option for the type of a null pointer. Let's say pointers in our language are represented as a parametrized type named `Ptr` which we can use like `Ptr<Int>`. Then, we can give `null` the type `forall A. Ptr<A>`. Of course, whether null pointers should exist at all is a separate question.  
> **username_18** (2024-03-09): @DavidYoung Isn’t that a reference to the top type? I’d normally expect the type checker to allow a value of that type to be used as any kind of pointer, which I would not want.  
> **username_18** (2024-03-09): @DavidYoung I do suggest that as a solution for a generic reference. I would recommend against it as the type of an error code.  
> **username_6** (2024-03-09): @Davislor What I mean is if you want the kind of setup like C where `null` can be used for any pointer type. Also top types are not necessarily what I mean here. I am largely talking about a system without subtyping (in the usual sense anyway). I'm not sure what you mean by an error code. `null` does not necessarily indicate an error. In fact, `null` is really the *only* value that (potentially) makes sense to have with that type since such a value would need to be able to be used at any pointer type (assuming you aren't allowed to cast a integer literal to a pointer or something like that).  
> **username_18** (2024-05-30): Follow-up: C23 adds `nullptr_t` (from C++) as a replacement for the type of a bull pointer. This was motivated primarily by implementations that `#define NULL 0`, but where a `void*` and the `int` value `0` have different widths. This breaks `NULL` in any context where the compiler cannot deduce that the type is `void*` and not `int`, such as passing it to a variadic function. Apparently it’s impossible to get everyone to stop defining `NULL` as `0`.  
---
### Answer by username_24 on 2023-07-09. Score: 23
An advantage of having a unit type instead of a void type is: unit is a regular type that functions normally, whereas void is weird and creates special cases.
Consider C#: they have void instead of unit, and when you want to write a wrapper for a function that returns a value, you have to write it twice:
    // normal function
    T Wrap(Func f)
    {
        Console.WriteLine("Calling the wrapped function...");
        return f();
    }
    // special case to handle void
    void Wrap(Action f)
    {
        Console.WriteLine("Calling the wrapped function...");
        f();
    }

If C# had chosen unit instead of void, you would simply call Wrap() with a Func. void and also Action would become unneeded.
> **Comments:**
> **username_12** (2023-07-09): Is this a duplicate of my answer?  
> **username_24** (2023-07-09): They both kinda make the same point, yes. I guess you win because you posted 3 minutes before me. But I think my example is clearer.  
> **username_25** (2023-07-10): As Java successfully used `Void` in its generics I'm surprised that C# made it worse.  
> **username_13** (2023-07-11): I believe in C++ it's legal to write `return f();` if `f()` is template-dependent and is of type `void` when the templates are filled in.  
> **username_26** (2023-07-11): It doesn't need to involve templates. `return f();` where `f()` returns `void` is perfectly legal C++. The same holds for C. I am not sure why C# decided to make it more cumbersome in this aspect. See the answer by @abel1502 for details.  
---
### Answer by username_22 on 2023-07-10. Score: 10
Type Theory
It's never a bad idea to start with some theory. In programming language theory, a type is akin to the set of values that instances of the type can take.
Some types are a finite set: a boolean has 2 values, a 32-bits integer a little over 4 billions. Others are an infinite set: strings, lists, etc...
Empty and Unit are very different there:

Empty: no value, the empty set.
Unit: one value, a singleton set.

Unit types and Empty types in the wild
First of all, unit types just exist. Even in C!
A struct with no field, or similarly a tuple or any other product type with no field, is a unit type. An array of 0 elements is a unit type. You can create a value, and all values are equivalent.
Similarly, empty types just exist. Even in C!
A union with no field, or any other sum type with no alternative, is an empty type. You cannot create a value.
Unless you go out of your way to forbid them in a language, they emerge spontaneously.
No value?
This is correct, an Empty type cannot be instantiated.
This has several implications:

A function taking an Empty type as argument can never be called.
A function returning an Empty type... actually will never return.
A generic sum type with an Empty type in some alternative will never have an instance of that alternative.

Empty types are very useful markers, allowing to cull away execution paths that cannot exist. This is useful for communicating intent to other humans and tools alike.
For example, let's have a look at the definition of abort in C++ and Rust:
[[noreturn]] void abort();

fn abort() -> !;

C++ requires a dedicated attribute, which is only usable in return position, and doesn't mesh well with meta-programming.
Rust, on the other hand, treats ! as just another type1. It can be used everywhere a type is expected, it's accessible via meta-programming, etc...
1 Ahem, well, it's not quite just another type yet.
What of C's void?
C's void is a strange beast, behaving at times like a unit type and at times like an empty type.
When denoting the parameters or return type of a function, it doesn't denote a function that cannot be called, or will never return, so is somewhat like a unit type -- except there is not really any value.
Still, it's a bit weird:
void foo(int);    //  One argument.
void bar(void);   //  No argument, of course!

Yet, no field of type void may exist in a struct or union, and no variable of type void may be declared. The latter, notably, means that direct chaining of return values is possible, but attempting to do so in two steps is not:
void foo();

void ok() {
    return foo();
}

void error() {
    void v = foo();
    return v;
}

Code generation, whether with external tools or macros, and in C++ templates, particularly suffer from this "special case".
Uniformity, Regularity
It's easier to shun C's void, and properly use unit and empty types instead.
It's easier both for the compiler writer and for the user, because it eliminates special cases!
From the user end, more specifically:

The less "special cases", the easier it is to learn a language.
The less "special cases", the more "brain cells" available for actual problem solving.
The less "special cases", the less issues in generic code.

The only cons, really, is unfamiliarity for people who have no been exposed to the concept previously... and they'll be converted quickly once they realize how much it simplifies their life.
Regrets
I remember reading that Anders Hejlsberg, the creator of C#, infamously regretted the irregularity of void in C# due the difficulties this introduced in generic code.
I cannot find a citation, but I can point at TypeScript -- his next brainchild -- where he kept void, but made it more regular instead. You can have variables of type void in TypeScript, though they can only be of value undefined.
Still named void, but not quite like C's void.
> **Comments:**
> **username_16** (2023-07-11): "A function returning a Void type... actually will never return." Why couldn't it just be unusable in expressions, the way C's actual `void` actually works? Why does the lack of a return value prevent `return`ing, if the code is not permitted to *use* the missing return value?  
> **username_22** (2023-07-11): @KarlKnechtel: To return a value, you must create it. A value of a Void type cannot be created, hence you cannot call return with it.  
> **username_22** (2023-07-11): @Seggan-OnStrike: Your question asks about unit vs c-void. This answer explains what unit types and empty types are, which should make it evident that c-void is a weird mix of both, sometimes behaving like a unit type (you can return it) sometimes like an empty type (you can't create a value of it, nor a variable with its type). If it's not as clear as I hoped, let me know what you're missing and I'll try to clarify it.  
> **username_16** (2023-07-11): @MatthieuM. That isn't the question. Why does returning from a function entail returning *a value*?  
> **username_22** (2023-07-11): @KarlKnechtel: It doesn't entail it, but it creates a corner case -- a difference between functions that return values vs functions that don't -- which makes the life of users more complicated in "generic" code, be it code generators, macros, templates, generics, ...  
> **username_23** (2023-07-12): "An array of 0 elements is a unit type. You can create a value, and all values are equivalent". Both for the empty struct and an array with 0 elements, this claim is not true in C. `malloc(0) != malloc(0)` is perfectly acceptable in the standard (the behavior is implementation defined), same goes for two statically allocated arrays of size 0. This violates the usual definition of a unit type.  
> **username_22** (2023-07-12): @Voo: `malloc` does NOT create an instance of a unit type, it allocates (raw) memory.  
> **username_23** (2023-07-12): @Matthieu I don't think C uses the terminology of "instance of a type" in the standard, so it's probably open to interpretation what that'd be. But in any case even for the alternative `int x[0]; int y[0]` case those won't compare equal either.  
> **username_22** (2023-07-13): @Voo: They do, as in `memcmp(x, y, sizeof(x)) == 0` (https://godbolt.org/z/jWebnWra9). As for "instance of a type", note that this answer starts with _programming language theory_, talking about programming language _in general_. Indeed, the section you talk about mentions _tuples_ and there's none in C.  
> **username_23** (2023-07-13): @Matthieu The part that I cite is about the claim that _C_ has a unit type, so clearly it's about C. But yes if you define equivalence in C in terms of memcmp then that should work. But then that also works for malloc(). Although usually equivalence classes are defined in terms of the relational operators - otherwise, every language can have a unit type.  
> **username_23** (2023-07-13): But I'll freely admit that I was in a nitpicking mood, mostly because the missing Unit Type in C# just caused me troubles the last few days again.  
---
### Answer by username_12 on 2023-07-09. Score: 8
Generic callback arguments
Consider a function with a generic type argument, such as the resolve callback to a TS Promise:
let x = new Promise((resolve, reject) => {
    // resolve has type (T) => void
});

If void is not usable in generic types, as in Java and C#, these have to be special-cased. And indeed they are: C# has different types Task and Task. Having a first-class unit type makes library code like this simpler and less redundant.
On the other hand, if (as in C) an argument list of (void) means "no arguments", this becomes easier on the caller. To call a function of type (Void) -> Void in Swift requires an extra pair of parens, foo(()), which just serves as visual noise.
---
### Answer by username_8 on 2023-07-09. Score: 6
I'd say there is no con to having a unit type. Even C++, while inheriting void from C has a unit type standardized in the form of std::monostate. Moreover, even with void, C and C++ allow constructs like:
void f() {}

void g() { return f(); }

So really, I'd say void type is merely a slightly restricted version of unit type. If your language has some custom weird behaviours related to void (like forbidding its use in structures, perhaps), you may, in the worst case, extend them to the unit type too. Regardless, the users will probably appreciate the increased generality.
> **Comments:**
> **username_9** (2023-07-09): Though C++'s `void` gets hairy when you try to assign the result of a `void` function to a local variable temporarily. It's really easy to make a template function accidentally stop working when `T = void` by changing something trivial like that; trust me, I've been there.  
---
### Answer by username_10 on 2023-07-10. Score: 5
It could make sets a special case of maps. That is, `std::map` works very like `std::set`. In a theoretical new language we could make the way we use both of them identical.

I don't know a case that makes it obviously useful except for writing less code implementing both of them, though.
> **Comments:**
> **username_11** (2023-07-10): That is an example of a unit type though. That it works in C++ is because C++ special-cases this particular use.  
> **username_10** (2023-07-10): @Longinus I don't think it works in C++. At least it doesn't compile in g++. The point is if void was a unit type, this could easily be supported, as something in the pros list. (Also, the downvoter has reversed their vote after my edit.)  
> **username_11** (2023-07-10): Ah I see, I misunderstood what you wrote. _(Also why did I believe C++ allowed that? I don't know)_  
---
### Answer by username_17 on 2023-07-24. Score: 3
In some contexts, void means that the function might have a return value, but the caller isn't supposed to use it. Consider this in Typescript:
function doSomethingAndThen(callback: () => void) {
    doSomething();
    callback();
}

In Typescript, it's legal to provide a callback which does return something, e.g. () => someArray.pop(), because () => T is a subtype of () => void for all T. This is convenient for the programmer, to not have to write the more verbose () => { someArray.pop(); }. It also allows things like  doSomethingAndThen(somethingElse), where somethingElse is a named function which already exists but has a return type.
This would be unsound if void were supposed to be a unit type like undefined (that is, the unit type whose only member is the value undefined), because the expression callback() would have type undefined despite not necessarily having the value undefined.
So, when void appears in return position like () => void, it is more analogous to a top type like unknown than a unit type. On the other hand, it's more useful to annotate functions as returning void rather than unknown, since the former indicates that even if some value is returned, it is meaningless and shouldn't be used.
---
### Answer by username_13 on 2023-07-11. Score: -2
Disadvantage: the language lets you do more silly things. Everywhere there isn't a value, you can treat it as a value and you get some nonsense like `unit` or `()`. For example, Haskell is happy to let you store `()` in a variable and print it.

All else being equal, a language that prevents more mistakes is a better language.
> **Comments:**
> **username_15** (2023-07-11): I don't think this is a disadvantage. Being able to generalise functions that expect to store something, so that they don't actually store anything useful, is a good feature  
> **username_16** (2023-07-11): Why is it "silly" or "a mistake" to do so?  
> **username_13** (2023-07-11): @KarlKnechtel What do you expect print(print(42)) to do? I think if you interview 100 programmers who don't already know your language or similar ones, they probably didn't say 42unit or 42()  
> **username_0** (2023-07-24): That still doesn't explain why it's "silly" or a "mistake"  
> **username_13** (2023-07-24): @Seggan-OnStrike I shouldn't have to explain why print(print(42)) is "silly" or a "mistake" - it should be readily apparent!  
> **username_14** (2023-10-14): Languages that remove functionality because it's allegedly error prone are all good until it prevents someone from doing something because they think it's a mistake but in reality they know what they're doing.  
> **username_13** (2023-10-17): @user16217248 Then I suppose C++ is the only language you use. **Every** other language has gone down the path of removing features to achieve the best power-to-weight ratio. C++ is the only language which focuses on adding many features to achieve the most raw power.  
> **username_14** (2023-10-17): @user253751 Well technically I use C more but yeah basically. I think C++ is great.  
---
