Funciones en Zig
Las funciones son bloques de código reutilizables que realizan tareas específicas. En Zig, las funciones ofrecen una gran flexibilidad y potencia, permitiendo desarrollar código modular, eficiente y seguro. Este artículo explorará las características de las funciones en Zig, desde lo básico hasta conceptos más avanzados.
Declaración de funciones básicas
Para declarar una función en Zig, se utiliza la palabra clave fn
seguida del nombre de la función, los parámetros entre paréntesis y el tipo devuelto:
const std = @import("std");
fn sumar(a: i32, b: i32) i32 {
return a + b;
}
test "función básica" {
const resultado = sumar(5, 3);
try std.testing.expect(resultado == 8);
}
Si una función no devuelve ningún valor, se utiliza void
como tipo de retorno:
fn imprimirSaludo() void {
std.debug.print("Hola, mundo!\n", .{});
}
Parámetros de una función
Los parámetros de una función en Zig son inmutables por defecto. Si necesitas modificar un parámetro dentro de la función, debes pasarlo como puntero:
fn incrementar(valor: *i32) void {
valor.* += 1;
}
test "modificar parámetro por referencia" {
var x: i32 = 10;
incrementar(&x);
try std.testing.expect(x == 11);
}
Retorno de valores
En Zig, una función puede devolver cualquier tipo:
fn obtenerMayor(a: i32, b: i32) i32 {
if (a > b) return a;
return b;
}
fn esPositivo(numero: i32) bool {
return numero > 0;
}
También es posible devolver múltiples valores usando estructuras o tuplas:
fn divMod(a: i32, b: i32) struct { cociente: i32, resto: i32 } {
return .{
.cociente = @divTrunc(a, b),
.resto = @rem(a, b),
};
}
test "retorno de múltiples valores" {
const resultado = divMod(10, 3);
try std.testing.expect(resultado.cociente == 3);
try std.testing.expect(resultado.resto == 1);
}
Funciones con errores
Zig soporta el manejo de errores a través de tipos de unión de error. Una función que puede fallar retorna un tipo de unión de error, indicado por !
:
fn dividir(a: f32, b: f32) !f32 {
if (b == 0) return error.DivisionPorCero;
return a / b;
}
test "función con manejo de errores" {
// Probar caso exitoso
const resultado = try dividir(10, 2);
try std.testing.expect(resultado == 5);
// Probar captura de error
_ = dividir(10, 0) catch |err| {
try std.testing.expect(err == error.DivisionPorCero);
return;
};
// Si llegamos aquí, es porque no se capturó el error esperado
unreachable;
}
Parámetros de tiempo de compilación
Zig permite definir parámetros que se resuelven en tiempo de compilación usando la palabra clave comptime
:
fn max(comptime T: type, a: T, b: T) T {
if (a > b) return a;
return b;
}
test "función con parámetro comptime" {
const enteroMayor = max(i32, 10, 20);
try std.testing.expect(enteroMayor == 20);
const flotanteMayor = max(f32, 10.5, 20.5);
try std.testing.expect(flotanteMayor == 20.5);
}
Este es un ejemplo de "programación genérica" en Zig, donde la función puede trabajar con cualquier tipo que cumpla los requisitos (en este caso, que soporte el operador >
).
Inferencia de tipo en los parámetros
Zig permite declarar parámetros con anytype
, lo que hace que el tipo se infiera en el momento de la llamada:
fn cuadrado(valor: anytype) @TypeOf(valor) {
return valor * valor;
}
test "inferencia de tipo de parámetro" {
const entero = cuadrado(5);
try std.testing.expect(@TypeOf(entero) == comptime_int);
try std.testing.expect(entero == 25);
const flotante = cuadrado(5.0);
try std.testing.expect(@TypeOf(flotante) == comptime_float);
try std.testing.expect(flotante == 25.0);
}
Punteros a funciones
En Zig, puedes guardar y pasar referencias a funciones:
fn sumar(a: i32, b: i32) i32 {
return a + b;
}
fn restar(a: i32, b: i32) i32 {
return a - b;
}
fn aplicarOperacion(a: i32, b: i32, operacion: *const fn (i32, i32) i32) i32 {
return operacion(a, b);
}
test "punteros a funciones" {
try std.testing.expect(aplicarOperacion(5, 3, sumar) == 8);
try std.testing.expect(aplicarOperacion(5, 3, restar) == 2);
}
Atributos de funciones
Zig proporciona varios modificadores para funciones:
Export
El modificador export
hace que una función sea visible para otros módulos o para código C externo:
export fn sumarExportada(a: i32, b: i32) i32 {
return a + b;
}
Extern
La palabra clave extern
declara una función que será resuelta en tiempo de ejecución:
extern "c" fn printf(format: [*:0]const u8, ...) c_int;
Inline
El modificador inline
fuerza a que una función sea insertada en lugar de llamada:
inline fn doble(x: i32) i32 {
return x * 2;
}
Funciones variádicas
Zig soporta funciones con número un variable de argumentos:
fn sumarTodos(args: []const i32) i32 {
var suma: i32 = 0;
for (args) |arg| {
suma += arg;
}
return suma;
}
test "función variádica" {
const numeros = [_]i32{1, 2, 3, 4, 5};
try std.testing.expect(sumarTodos(&numeros) == 15);
}
Convenciones de llamada
Zig permite especificar la convención de llamada de una función, lo que es útil para la interoperabilidad con otros lenguajes:
fn funcionNormal() void {}
fn funcionStdcall() callconv(.Stdcall) void {}
fn funcionC() callconv(.C) void {}
Recursión
Las funciones en Zig pueden ser recursivas, llamándose a sí mismas:
fn factorial(n: u64) u64 {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
test "función recursiva" {
try std.testing.expect(factorial(5) == 120);
}
Funciones con tipos genéricos
Una característica poderosa de Zig es la capacidad de crear funciones con tipos genéricos:
fn Lista(comptime T: type) type {
return struct {
items: []T,
len: usize,
pub fn init(items: []T) @This() {
return .{
.items = items,
.len = items.len,
};
}
pub fn get(self: @This(), index: usize) ?T {
if (index >= self.len) return null;
return self.items[index];
}
};
}
test "función con tipos genéricos" {
var numeros = [_]i32{1, 2, 3, 4};
var lista = Lista(i32).init(&numeros);
try std.testing.expect(lista.len == 4);
try std.testing.expect(lista.get(2).? == 3);
try std.testing.expect(lista.get(5) == null);
}