Ir al contenido principal

Variables en Zig

Las variables constituyen el elemento fundamental de cualquier lenguaje de programación, siendo los espacios de memoria donde almacenamos valores durante la ejecución de nuestro programa. Zig presenta un enfoque claro y sistemático respecto a las variables, priorizando la seguridad y el control explícito sobre la memoria.

Variables mutables e inmutables

En Zig existen dos tipos principales de variables:

  • Variables inmutables (const): Una vez asignadas, no pueden cambiar su valor.
  • Variables mutables (var): Pueden modificar su valor después de la asignación inicial.
const constante: u32 = 1024;  // Variable inmutable de tipo entero sin signo de 32 bits
var variable: i32 = -5;       // Variable mutable de tipo entero con signo de 32 bits

// constante = 2048;  // ¡Error de compilación! No se puede modificar una constante
variable = 10;       // Correcto, las variables mutables pueden cambiar

Como práctica recomendada, es preferible utilizar const siempre que sea posible. Esto no solo comunica la intención de que el valor no debería cambiar, sino que también permite al compilador realizar ciertas optimizaciones y evita errores accidentales de modificación.

Declaración de tipos

Zig ofrece flexibilidad en la declaración de los tipos de las variables:

Declaración explícita

El tipo se especifica directamente en la declaración mediante la sintaxis : tipo después del identificador:

const edad: u8 = 30;          // Entero sin signo de 8 bits
const altura: f32 = 1.75;     // Punto flotante de 32 bits
const nombre: []const u8 = "Ana"; // Slice de caracteres inmutables

Declaración implícita (inferencia de tipos)

El compilador puede deducir el tipo basándose en el valor asignado:

const edad = 30;        // Se infiere como entero (comptime_int)
const altura = 1.75;    // Se infiere como flotante (comptime_float)
const nombre = "Ana";   // Se infiere como *const [3:0]u8 (string literal)

La inferencia de tipos en Zig es conservadora y precisa. En los ejemplos anteriores, los números son tratados como valores de tiempo de compilación (comptime_int y comptime_float) hasta que se asignan a una variable de tipo específico.

Inicialización y undefined

Una característica distintiva de Zig es que toda variable debe tener un valor asignado al ser declarada. Esto refuerza la seguridad del código al evitar el uso de valores no inicializados.

Si no es posible proporcionar un valor significativo durante la declaración, se puede usar el valor especial undefined:

var buffer: [100]u8 = undefined;  // Se reserva espacio para 100 bytes sin inicializarlos
const estructura: MiEstructura = undefined; // Se reserva espacio para la estructura

// Posteriormente se asignan valores reales
buffer[0] = 65; // 'A' en ASCII

Es importante entender que undefined no es "un valor especial que representa la ausencia de valor". En su lugar, significa "no me importa qué haya en esta memoria ahora mismo, voy a escribir algo antes de leerla". Usar memoria marcada como undefined sin inicializarla primero puede llevar a comportamientos impredecibles.

Coerción de tipos

La coerción de tipos en Zig permite convertir valores entre tipos compatibles. Existen dos mecanismos principales:

Coerción implícita

Zig permite coerciones implícitas cuando son seguras y no hay pérdida de información:

const pequeño: u8 = 5;        // Entero de 8 bits
const grande: u16 = pequeño;  // Coerción implícita a entero de 16 bits

Esto funciona porque un u8 siempre puede representarse como un u16 sin perder información.

Coerción explícita con @as

Para casos donde se requiere una conversión explícita, se utiliza la función @as:

const grande: u16 = 1000;
// const pequeño: u8 = grande;  // ¡Error! Un u16 no cabe necesariamente en un u8
const pequeño: u8 = @as(u8, 100);  // Correcto, el valor 100 es compatible con u8

La función @as intenta convertir el valor al tipo especificado, pero fallará en tiempo de compilación o producirá un error en tiempo de ejecución si la conversión causa la pérdida de información.

Ámbito de las variables

Las variables en Zig tienen un ámbito (scope) determinado por el bloque en el que se declaran:

{
    const x: i32 = 10;  // x solo existe dentro de este bloque
    {
        const y: i32 = 20;  // y solo existe dentro de este sub-bloque
        // Aquí podemos acceder tanto a x como a y
    }
    // Aquí solo podemos acceder a x, y ya no existe
}
// Aquí ni x ni y existen

Además, Zig no permite el sombreado (shadowing) de variables, es decir, no se puede declarar una nueva variable con el mismo nombre dentro del mismo ámbito o un ámbito anidado:

const x: i32 = 10;
{
    // const x: i32 = 20;  // ¡Error! No se permite sombrear la variable x
}

Variables a nivel de contenedor

Las variables declaradas en el nivel superior de un archivo o dentro de una estructura se llaman "variables a nivel de contenedor". Estas variables tienen un comportamiento especial:

const PI: f32 = 3.14159;  // Variable global inmutable
var contador: u32 = 0;    // Variable global mutable

pub fn incrementarContador() void {
    contador += 1;        // Modificamos la variable global
}

Las variables a nivel de contenedor siguen las mismas reglas de mutabilidad que las variables locales, pero tienen un tiempo de vida que dura toda la ejecución del programa.

Conclusión

El sistema de variables de Zig combina la simplicidad y la seguridad a través de reglas claras y coherentes. La distinción entre const y var promueve un estilo de programación que minimiza el estado mutable, mientras que la obligación de inicializar todas las variables ayuda a prevenir errores comunes relacionados con valores no inicializados.

La coerción de tipos en Zig es conservadora y segura, permitiendo conversiones solo cuando no hay riesgo de pérdida de información o comportamientos inesperados. Esta filosofía de diseño refleja el enfoque general de Zig: proporcionar control explícito sobre el comportamiento del código, evitando sorpresas y optimizando la claridad y la seguridad.