En Zig, los punteros a función son tratados como punteros normales, pero su uso en ciertos contextos, como el despacho dinámico o la serialización, puede requerir convertirlos a un entero. El builtin @ptrToInt facilita esa conversión, convirtiendo cualquier puntero, incluido uno que apunte a una función, en un usize. Este artículo desglosa cómo funciona, cuándo y cómo aplicarlo de forma segura.
Entendiendo @ptrToInt
- Qué hace: Toma un puntero y devuelve su dirección de memoria representada como
usize. - Restricciones: El puntero debe ser válido y alineado adecuadamente. En Zig,
@ptrToIntadmite cualquier puntero, incluso punteros a funciones. - Usos comunes: Comparación de punteros, hashing, índices de tabla, serialización de direcciones, etc.
Funciones y punteros a función en Zig
Antes de convertir un puntero a función, veamos cómo se declara y se usa una función básica.
const std = @import("std");
/// Una función simple que suma dos enteros
fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn main() void {
// Llamar directamente a la función
const result = add(2, 3);
std.debug.print("Resultado: {}\n", .{result});
}
En el código anterior, add es una función cuyo nombre es un puntero al código que se ejecutará cuando se invoque.
Obtener un puntero a función
Para usar @ptrToInt, primero necesitamos un puntero explícito:
const std = @import("std");
fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn main() void {
// Declaramos un tipo de función con la misma firma que add
const Func = fn (i32, i32) i32;
// Asignamos la función a una variable de tipo Func
var fn_ptr: *const Func = &add;
// Podemos llamar a la función a través del puntero
const result = fn_ptr(4, 5);
std.debug.print("Resultado: {}\n", .{result});
}
Observa que el puntero fn_ptr tiene tipo *const Func, lo que indica que apunta a una función constante.
Convertir puntero a función a entero con @ptrToInt
Ahora veremos cómo usar @ptrToInt para transformar ese puntero en un entero.
const std = @import("std");
fn add(a: i32, b: i32) i32 {
return a + b;
}
pub fn main() void {
const Func = fn (i32, i32) i32;
var fn_ptr: *const Func = &add;
// Convertimos el puntero a función en un usize
const ptr_as_int: usize = @ptrToInt(fn_ptr);
std.debug.print("Dirección de add como entero: {x}\n", .{ptr_as_int});
}
En la línea donde se llama a @ptrToInt, el compilador verifica que fn_ptr sea un puntero válido y que el tipo de dato de destino sea usize. El resultado es la dirección de memoria donde está ubicada la función add.
Usar el entero como índice o tabla de despacho
Un caso típico de @ptrToInt es usar la dirección de una función como clave en una tabla hash o para construir un dispatch table basado en la identidad de la función. A continuación, se muestra cómo crear una tabla de funciones y acceder a ellas mediante su dirección convertida.
const std = @import("std");
/// Tres funciones auxiliares
fn inc(a: i32) i32 { return a + 1; }
fn dec(a: i32) i32 { return a - 1; }
fn dbl(a: i32) i32 { return a * 2; }
// Definimos el tipo de función con un parámetro y un retorno
const Func = fn (i32) i32;
// Estructura simple para almacenar la dirección y la función
const FuncEntry = struct {
addr: usize,
fn_ptr: *const Func,
};
pub fn main() void {
// Creamos un array con nuestras funciones
const funcs = [_]FuncEntry{
.{ .addr = @ptrToInt(&inc), .fn_ptr = &inc },
.{ .addr = @ptrToInt(&dec), .fn_ptr = &dec },
.{ .addr = @ptrToInt(&dbl), .fn_ptr = &dbl },
};
// Supongamos que queremos ejecutar la función que corresponde a una dirección dada
const target: usize = @ptrToInt(&dbl); // Imaginemos que lo conocemos de alguna fuente externa
// Buscamos la entrada que coincida con la dirección
for (funcs) |entry| {
if (entry.addr == target) {
// Ejecutamos la función encontrada
const val = entry.fn_ptr(21);
std.debug.print("Resultado de la función con dirección {x}: {}\n", .{entry.addr, val});
return;
}
}
std.debug.print("Función no encontrada\n", .{});
}
Este ejemplo demuestra cómo @ptrToInt permite usar direcciones de función como claves. En la práctica, podrías llenar la tabla a partir de un enumerado o de una configuración externa.
Precauciones y buenas prácticas
- No confundir con
comptime:@ptrToIntopera en tiempo de ejecución. Si necesitas la dirección en tiempo de compilación, usa@ptrToInt(comptime fn), pero solo dentro de funcionescomptime. - Alineación: Asegúrate de que el puntero sea válido y esté alineado al tamaño de
usize. Los punteros a funciones en Zig suelen cumplir con esta condición. - Portabilidad: La representación de una función como entero no es portable entre diferentes arquitecturas o ejecuciones. Evita escribir la dirección a disco si la intención es usarla en otra sesión.
- Comparaciones: Se pueden comparar direcciones, pero no es recomendable usarlas como identificadores únicos en el mundo de la programación, ya que pueden cambiar entre ejecuciones.
Ejemplo completo: Despacho dinámico con @ptrToInt
const std = @import("std");
// Definimos el tipo de función
const Op = fn (i32, i32) i32;
// Funciones de operación
fn add(a: i32, b: i32) i32 { return a + b; }
fn sub(a: i32, b: i32) i32 { return a - b; }
fn mul(a: i32, b:
