Unions
Cuando declaramos un union, definimos un tipo de dato que puede contener uno entre varios tipos posibles.
Esos tipos posibles que puede adoptar los declaramos en el cuerpo del union de la siguiente forma:
const NombreUnion = union {
nombre_campo: tipo,
…
nombre_campoN: tipo,
};
Solo uno de estos tipos puede estar activo al mismo tiempo.
Union Simple (bare union)
La forma base que hemos indicado para declarar un union se denomina “unión simple” (bare union) y tiene un inconveniente: si el tipo activado, no se usa inmediatamente - por ejemplo lo devolvemos desde una función - no sabemos cuál de todos los tipos definidos fue activado.
Union etiquetado (Tagged union)
Para saber qué tipo está activo en cada momento, necesitamos declarar un union etiquetado (tagged union).
Esto se puede hacer de dos maneras:
Union etiquetado mediante enum previo
La primera manera consiste en declarar un enum previo que sirve para etiquetar el union:
const NombreEnumParaEtiquetado = enum {
nombre_del_valor,
…
nombre_del_valor_N,
};
const NombreUnion = union(NombreEnumParaEtiquetado) {
nombre_del_valor: tipo,
…
nombre_del_valorN: tipo,
};
En este tipo de “etiquetado” mediante un enum, el union tiene que llevar los campos con los mismos nombres definidos en el enum y especificar el tipo de cada uno.
Union etiquetado por inferencia
Si no queremos - o no necesitamos - definir el enum aparte para el etiquetado de los campos, podemos esta forma:
const NombreUnion = union(enum) { // <- palabra enum entre paréntesis
nombre_campo: tipo,
…
nombre_campoN: tipo,
};
Al definir un union así, Zig inferirá automáticamente las etiquetas sin necesidad de definirlas en un enum separado.
Puedes instanciar un union sin tener que escribir su nombre, siempre que el dato al que lo asignas ya tenga ese tipo declarado:
|
const MyUnion = union(enum){ field1: int, field2: float, // ... } // con un .{ sin el nombre - literal anónimo const u_my1: MyUnion = .{ .field1 = 42 }; // con el nombre explícito const u_my2: MyUnion = MyUnion{ .field2 = 13.14 }; |
La sintaxis - .{ } - se llama literal anónimo porque el valor se construye sin mencionar explícitamente el nombre del tipo que representa. También lo podemos usar en enums y como ya sabes lo usamos en los structs para crear tuplas.
Vamos a usar un union que hemos llamado EngineSocket para describir el sitio del ala donde puede montarse un motor- ese hueco podrá quedar vacío: .empty = true, o tener un motor montado: .engine=DroneEngine{… , pero no podrá tener ambas cosas a la vez.
union_2.zig |
|
const std = @import("std"); const print = std.debug.print; // errores posibles de motor const EngineError = error{ Damaged, Autorepairing, NotConnected }; // posibles estados de un motor const EngineStatus = enum(i8) { failure = -1, off = 0, on = 1, testing = 2, autorepairing = 3, }; // motor como struct const DroneEngine = struct { var n_total_failed: u8 = 0; const N_MAX_RPM: u32 = 8500; const N_MIN_HEALTH: u32 = 75; e_status: EngineStatus, n_health: u8, s_key: []const u8, n_rpm: u32, // método que formatea el struct y se invoca // automáticamente cuando lo imprimimos pub fn format(self: DroneEngine, writer: anytype) !void { try writer.print( \\Motor {s}: \\ * rpm: {} \\ * health: {} \\ * status: {s} , .{ self.s_key, self.n_rpm, self.n_health, @tagName(self.e_status), }); } }; const EngineSocket = union(enum) { engine: DroneEngine, empty: bool, // lo que imprimimos depende del tipo de dato pub fn format(self: EngineSocket, writer: anytype) !void { switch (self) { .engine => |o_eng| { try writer.print("{f}", .{o_eng}); }, .empty => { try writer.print("-=VACIO=-", .{}); }, } } }; const Wing = struct { const N_ENGINE_SOCKETS: u8 = 4; // al inicio tenemos 4 sitios para motores a_sockets: [Wing.N_ENGINE_SOCKETS]EngineSocket = .{ EngineSocket{ .empty = true, }, } ** Wing.N_ENGINE_SOCKETS, // montar los motores en el ala fn mount_engines(self: *Wing, a_engines2mount: []const DroneEngine) void { // pasamos por todos los sockets for (0..Wing.N_ENGINE_SOCKETS) |n_i| { // si viene un motor para montar y si no dejamos vacío self.a_sockets[n_i] = if (n_i < a_engines2mount.len) .{ .engine = a_engines2mount[n_i] } else .{ .empty = true }; } } // imprimimos el estado de los motores fn show_state(self: *Wing) void { print("-------------------------------------------------\n", .{}); for (&self.a_sockets) |*o_engine_socket| { print("{f}\n", .{o_engine_socket}); } print("\n{} fallos\n", .{DroneEngine.n_total_failed}); } }; // todos los motores disponibles const a_all_engines = [_]DroneEngine{ DroneEngine{ .s_key = "E1", .n_health = 100, .n_rpm = 0, .e_status = .off }, DroneEngine{ .s_key = "E2", .n_health = 95, .n_rpm = 0, .e_status = .off }, DroneEngine{ .s_key = "E3", .n_health = 100, .n_rpm = 0, .e_status = .off }, DroneEngine{ .s_key = "E4", .n_health = 98, .n_rpm = 0, .e_status = .off }, }; pub fn main() void { // el ala vacía var o_wing = Wing{}; // mostrar el ala vacía o_wing.show_state(); // montamos 4 de los motores disponibles o_wing.mount_engines(a_all_engines[0..2]); // mostramos el estado del ala o_wing.show_state(); } |
|
$ zig run union_2.zig |
|
------------------------------------------------- -=VACIO=- -=VACIO=- -=VACIO=- -=VACIO=- 0 fallos ------------------------------------------------- Motor E1: * rpm: 0 * health: 100 * status: off Motor E2: * rpm: 0 * health: 95 * status: off -=VACIO=- -=VACIO=- 0 fallos |
En el ejemplo anterior, el jugador solo había montado 2 motores en el “ala”. ¿Sabrías encontrar el número que hay que cambiar para que, solo modificando ese dato se monten los 4 motores necesarios?
Sobrevuelan las olas del mar profundo, que de noche, en la oscuridad, reflejan la luz de la luna, como una enorme cota de malla hecha de plata. Nota la brisa caprichosa, - momento caricia, momento látigo - en la cara y los pies helados que, a veces, casi tocan la superficie del mar. A ratos planean; a ratos se encienden los motores con zumbido salvaje, olor a metal quemado, sufriendo para mantenerse en el aire. Se apagan cuando el viento les lleva a favor, y así siguen una ruta que ahora mismo solo 1KR conoce y visualiza desde dentro del ala.
Al aterrizar en la costa pedregosa, después del largo vuelo, siente el dolor en las manos agarrotadas, molido por el cansancio. Desenchufa y arranca la cabeza metálica desde el ala.
- Hemos llegado peque. ¿Estás bien?
- Sí, estoy aquí padre. Lo hemos vuelto a conseguir.