We have said that functions are like machines that receive data, process it, and return a result.
But sometimes a function does its job and has nothing to return. This is where the keyword void comes into play.
At first glance, the concept is very simple. There are functions that return things, and functions that return nothing.
But as we will see, “returning nothing” is a real problem, and how some languages try to solve it with concepts like Unit.
void causes problems similar to null in programming. Basically, the possibility of “having nothing” or “returning nothing” doesn’t work very well.
Although null is infinitely worse, that doesn’t mean void is much better.
What is Void?
In classic imperative programming (C++, C#, Java), we use void to indicate that a function does not return any value.
It simply performs an action (a side effect) and finishes.
In C, void is used to declare functions that do not return a value.
#include <stdio.h>
// Function that does not return a value
void imprimirMensaje() {
printf("Hola, mundo!\n");
}
int main() {
imprimirMensaje();
return 0;
}
In Java, void is used similarly to C.
public class Main {
// Function that does not return a value
static void imprimirMensaje() {
System.out.println("Hola, mundo!");
}
public static void main(String[] args) {
imprimirMensaje();
}
}
In JavaScript, void is not a keyword to define functions, but the concept is similar. The saludar function does not return any value.
function saludar() {
console.log("Hola, mundo!");
}
saludar();
In Python, there is no void keyword. Functions that do not explicitly return a value return None.
# Function that does not return a value
def imprimir_mensaje():
print("Hola, mundo!")
# Call the function
imprimir_mensaje()
It’s like sending a letter to the mailbox without a return address. You drop it off, you leave, and you assume it will arrive. You don’t expect a response back.
So far, so good. It’s useful for:
- Writing to a log.
- Saving to a database (if we don’t need to confirm the ID).
- Changing the state of a global variable (although we don’t like this much).
The Problem with Void: It’s Not a Real Type
The problem with void is that, in many languages, it is not a real data type. It is a reserved word that means “absence of type”.
It seems silly, but it creates major inconsistencies when programming. For example, you cannot assign void to a variable.
var resultado = Saludar(); // Error! You cannot assign void to anything.
This becomes a headache when we try to use Generics or functional programming. Imagine you have a function that wraps the execution of other functions to measure time:
T MedirTiempo<T>(Func<T> funcion) { ... }
This function expects funcion to return “something” (T). If your function returns void, it doesn’t fit this pattern.
You have to write a duplicate version specifically for void. It’s dirty and redundant code.
The Functional Solution: The Unit Type
To solve this, functional languages (like Haskell, F#) and some modern ones (like Kotlin or Rust) use the Unit type.
The difference is subtle but vital:
- Void: It is the absence of a value. (There is no box).
- Unit: It is a type that has only one possible value. (There is a box, which always contains: “nothing”).
In Scala, the Unit type is used for functions that do not return a meaningful value. The saludar function returns Unit.
def saludar(): Unit = {
println("Hola, mundo!")
}
saludar()
In Kotlin, Unit is a type used similarly. The saludar function returns Unit.
fun saludar(): Unit {
println("Hola, mundo!")
}
saludar()
By using Unit, the function does return something: it returns an instance of Unit. This allows treating all functions equally.
If a function has nothing interesting to return, it returns Unit. This way, the type system doesn’t break and we can use generics without problems.
The Danger: Void in Asynchrony
Where void goes from being a “minor annoyance” to a possible catastrophic bug is in asynchronous programming (Async/Await).
In C# or JavaScript, when we do an asynchronous task, we usually return a Task or a Promise. These objects represent “a job that will be completed in the future”.
// Correct: Returns a Task. Whoever calls me can wait (await) for it to finish.
async Task GuardarDatosAsync() {
await database.SaveAsync();
}
But what happens if we use void in an asynchronous function?
// DANGER! Async Void
async void GuardarDatosMalAsync() {
await database.SaveAsync();
}
This is known as “Fire and Forget”.
Why is async void so bad?
- Cannot be awaited (
await): Since the function returnsvoid(nothing), the code that calls it has no Task object to wait for. The main program continues executing without knowing if the function finished or not. - Exceptions are lost: If an error occurs inside an
async void, no one catches it. The exception bubbles up to the synchronization context and often crashes the entire application without yourtry-catchbeing able to do anything.
That’s why, in modern programming, when a function doesn’t return data but is asynchronous, we never return void.
- In C#: We return
Task. (It’s like aUnitbut with asynchronous capabilities: “I promise I’ll let you know when I’m done, even if I don’t bring you any data”). - In JavaScript: We return
Promise<void>.
// JavaScript
async function procesar() { // Implicitly returns a Promise
await hacerAlgo();
}
