zig-funciones-sintaxis

Funciones en Zig: sintaxis, parámetros y retorno

  • 5 min

Una función en Zig es un bloque de código con nombre, parámetros y tipo de retorno explícito. Hasta ahora hemos escrito casi todo dentro de main, pero para hacer software mantenible necesitamos modularidad.

Las funciones en Zig son el mecanismo principal para dividir problemas complejos en tareas pequeñas y reutilizables.

Aunque la sintaxis te resultará familiar si vienes de C, Go o Rust, Zig introduce una idea importante respecto a los parámetros: su valor no se puede reasignar dentro de la función.

Anatomía de una función

La declaración de una función en Zig sigue una estructura muy limpia. Leemos de izquierda a derecha: palabra clave, nombre, parámetros y tipo de retorno.

fn nombreFuncion(parametro1: Tipo, parametro2: Tipo) TipoRetorno {
    // Cuerpo de la función
    return valor;
}
Copied!

Vamos a ver un ejemplo real de una función que suma dos enteros de 32 bits:

fn sumar(a: i32, b: i32) i32 {
    const resultado = a + b;
    return resultado;
}
Copied!
  1. fn: Indica que estamos definiendo una función (a diferencia de var o const).
  2. sumar: El nombre. Por convención, en Zig las funciones usan camelCase.
  3. (a: i32, b: i32): La lista de parámetros. Es obligatorio indicar el tipo de cada uno.
  4. i32 (al final): El tipo de dato que la función promete devolver.
  5. return: Devuelve el valor y termina la ejecución de la función.

Si la función no devuelve nada (es un procedimiento), el tipo de retorno debe ser explícitamente void.

Visibilidad con pub

Por defecto, una función no queda accesible para los módulos que importan el archivo donde se define. Para formar parte de la interfaz pública del módulo, debe declararse con pub.

Si queremos que una función sea accesible desde otros archivos (o que sea el punto de entrada como main), debemos usar la palabra clave pub.

// Solo visible en este archivo
fn funcionPrivada() void { ... }

// Visible desde cualquier archivo que importe este módulo
pub fn funcionPublica() void { ... }
Copied!

Los parámetros son inmutables

Aquí es donde Zig se diferencia drásticamente de C o C++.

En C, cuando pasas un valor a una función, se crea una copia local. Tú puedes modificar esa copia dentro de la función sin afectar al exterior.

// C (Válido)
void contador(int x) {
    x = x + 1; // Modificamos la copia local de x
}
Copied!

En Zig, esto está prohibido. Los parámetros de una función son considerados constantes (const).

// Zig (ERROR de compilación)
fn intentarModificar(x: i32) void {
    x = x + 1; // error: cannot assign to constant
}
Copied!

¿Por qué esta restricción?

Zig quiere que el flujo de datos sea claro. La función no puede reasignar el parámetro, aunque sí puede modificar la memoria apuntada si recibe un puntero mutable o un slice []T. La inmutabilidad afecta al valor del parámetro, no necesariamente a los datos a los que hace referencia.

¿Cómo lo solucionamos?

Si necesitas modificar un valor recibido para cálculos internos, simplemente crea una copia local mutable usando var:

fn calcular(entrada: i32) i32 {
    var copia_mutable = entrada;
    copia_mutable += 10;
    return copia_mutable;
}
Copied!

Valores de retorno y descarte

Zig es muy estricto con los datos. Si una función devuelve un valor, estás obligado a usarlo.

fn obtenerNumero() i32 {
    return 5;
}

pub fn main() void {
    obtenerNumero(); // Error: value of type 'i32' ignored
}
Copied!

Esto evita bugs donde llamamos a funciones que devuelven códigos de error o estados importantes y los ignoramos por despiste.

Si explícitamente quieres ignorar el resultado (porque solo te interesan los efectos secundarios de la función), debes asignarlo a la variable de descarte _:

pub fn main() void {
    _ = obtenerNumero(); // Correcto, "tiramos" el 5
}
Copied!

Recursividad

Zig soporta recursividad (una función que se llama a sí misma), pero debemos usarla con cuidado. Cada llamada consume espacio en la pila y Zig 0.16 no protege el programa frente a un desbordamiento de pila.

fn factorial(n: u64) u64 {
    if (n == 0) return 1;
    return n * factorial(n - 1);
}
Copied!

En programación de sistemas y embebidos, a menudo se prefiere convertir la recursividad en bucles (while o for) para garantizar un uso de memoria predecible y evitar reventar la pila.

Paso por valor o por referencia

Una duda común es: ¿Zig pasa los datos por valor (copiando) o por referencia (puntero)?

La respuesta corta es: tú defines la semántica y el compilador decide la representación más eficiente.

  • Para tipos primitivos (i32, bool, f64), se pasan por copia.
  • Para structs, uniones y arrays grandes, Zig puede pasarlos internamente por referencia si resulta más eficiente, pero la función conserva semántica de valor inmutable.

Si quieres modificar el valor original, debes expresarlo mediante un puntero o un slice mutable. Veremos ambos casos en el capítulo de punteros y memoria.