Hemos dicho que las funciones son como máquinas que reciben datos, los procesan y devuelven un resultado.
Pero a veces una función hace su trabajo, y no tiene nada que devolver. Aquí entra en juego la palabra clave void.
A primera vista, el concepto es es muy sencillo. Hay funciones que devuelven cosas, y funciones que no devuelven nada.
Pero como veremos, “no devolver nada” es un auténtico problema, y como algunos lenguajes lo intentan resolver como conceptos como Unit.
void da problemas similares a null en programación. Básicamente, la posibilidad de “tener nada” o “devolver nada” no funciona muy bien.
Aunque null es infinitamente peor , eso no significa que void sea mucho mejor.
¿Qué es Void?
En la programación imperativa clásica (C++, C#, Java), usamos void para indicar que una función no retorna ningún valor.
Simplemente realiza una acción (un efecto secundario) y termina.
En C, void se usa para declarar funciones que no devuelven un valor.
#include <stdio.h>
// Función que no devuelve un valor
void imprimirMensaje() {
printf("Hola, mundo!\n");
}
int main() {
imprimirMensaje();
return 0;
}
En Java, void se usa de manera similar a C.
public class Main {
// Función que no devuelve un valor
static void imprimirMensaje() {
System.out.println("Hola, mundo!");
}
public static void main(String[] args) {
imprimirMensaje();
}
}
En JavaScript, void no es una palabra clave para definir funciones, pero el concepto es similar. La función saludar no devuelve ningún valor.
function saludar() {
console.log("Hola, mundo!");
}
saludar();
En Python, no existe una palabra clave void. Las funciones que no devuelven un valor explícitamente devuelven None.
# Función que no devuelve un valor
def imprimir_mensaje():
print("Hola, mundo!")
# Llamar a la función
imprimir_mensaje()
Es como mandar una carta al buzón sin remitente. La echas, te vas, y asumes que llegará. No esperas una respuesta de vuelta.
Hasta aquí, todo correcto. Es útil para:
- Escribir en un log.
- Guardar en base de datos (si no necesitamos confirmar el ID).
- Cambiar el estado de una variable global (aunque esto no nos guste mucho).
El problema con Void: No es un tipo real
El problema de void es que, en muchos lenguajes, no es un tipo de dato real. Es una palabra reservada que significa “ausencia de tipo”.
Parece una tonteria, pero genera inconsistencias bien gordas cuando programas. Por ejemplo, no puedes asignar void a una variable.
var resultado = Saludar(); // ¡Error! No puedes asignar void a nada.
Esto se vuelve un dolor de cabeza cuando intentamos usar Genéricos o programación funcional. Imagina que tienes una función que envuelve la ejecución de otras funciones para medir el tiempo:
T MedirTiempo<T>(Func<T> funcion) { ... }
Esta función espera que funcion devuelva “algo” (T). Si tu función devuelve void, no encaja en este patrón.
Tienes que escribir una versión duplicada específica solo para void. Es código sucio y redundante.
La solución funcional: El tipo Unit
Para solucionar esto, los lenguajes funcionales (como Haskell, F#) y algunos modernos (como Kotlin o Rust) utilizan el tipo Unit.
La diferencia es sutil pero vital:
- Void: Es la ausencia de valor. (No hay caja).
- Unit: Es un tipo que tiene un solo valor posible. (Hay una caja, que siempre contiene: “nada”).
En Scala, el tipo Unit se utiliza para funciones que no devuelven un valor significativo. La función saludar devuelve Unit.
def saludar(): Unit = {
println("Hola, mundo!")
}
saludar()
En Kotlin, Unit es un tipo que se utiliza de manera similar. La función saludar devuelve Unit.
fun saludar(): Unit {
println("Hola, mundo!")
}
saludar()
Al usar Unit, la función sí devuelve algo: devuelve una instancia de Unit. Esto permite tratar a todas las funciones por igual.
Si una función no tiene nada interesante que devolver, devuelve Unit. Así, el sistema de tipos no se rompe y podemos usar genéricos sin problemas.
El peligro: Void en asincronía
Donde void pasa de ser una “pequeña molestia” a un `posible bug catastrófico es en la programación asíncrona (Async/Await).
En C# o JavaScript, cuando hacemos una tarea asíncrona, solemos devolver una Task o una Promise. Estos objetos representan “un trabajo que se completará en el futuro”.
// Correcto: Devuelve una Tarea. Quien me llame puede esperar (await) a que termine.
async Task GuardarDatosAsync() {
await database.SaveAsync();
}
Pero, ¿qué pasa si usamos void en una función asíncrona?
// ¡PELIGRO! Async Void
async void GuardarDatosMalAsync() {
await database.SaveAsync();
}
Esto se conoce como “Fire and Forget”.
¿Por qué es tan malo el async void?
- No se puede esperar (
await): Como la función devuelvevoid(nada), el código que la llama no tiene ningún objeto Task al que esperar. El programa principal sigue ejecutándose sin saber si la función terminó o no. - Las excepciones se pierden: Si ocurre un error dentro de un
async void, nadie lo captura. La excepción sube hasta el contexto de sincronización y, a menudo, crashea toda la aplicación sin que tutry-catchpueda hacer nada.
Por eso, en la programación moderna, cuando una función no devuelve datos pero es asíncrona, nunca devolvemos void.
- En C#: Devolvemos
Task. (Es como unUnitpero con capacidades de asincronía: “Te prometo que te avisaré cuando termine, aunque no te traiga ningún dato”). - En JavaScript: Devolvemos
Promise<void>.
// JavaScript
async function procesar() { // Implícitamente devuelve una Promise
await hacerAlgo();
}
