Zig Structs and Enums
Structs and enums are important tools for organizing and representing complex data in Zig. This chapter will cover their definition, usage, and best practices in detail.
Struct Basics
Struct Definition and Initialization
zig
const std = @import("std");
// Basic struct definition
const Point = struct {
x: f32,
y: f32,
};
// Struct with default values
const Rectangle = struct {
width: f32 = 1.0,
height: f32 = 1.0,
x: f32 = 0.0,
y: f32 = 0.0,
};
// Complex struct
const Person = struct {
name: []const u8,
age: u32,
height: f32,
is_student: bool,
};
pub fn main() void {
// Struct initialization
const point1 = Point{ .x = 3.0, .y = 4.0 };
const point2 = Point{ .y = 2.0, .x = 1.0 }; // Field order can differ
std.debug.print("Point 1: ({d:.1}, {d:.1})\n", .{ point1.x, point1.y });
std.debug.print("Point 2: ({d:.1}, {d:.1})\n", .{ point2.x, point2.y });
// Using default values
const rect1 = Rectangle{};
const rect2 = Rectangle{ .width = 5.0, .height = 3.0 };
std.debug.print("Rectangle 1: {}x{} at ({d:.1}, {d:.1})\n",
.{ rect1.width, rect1.height, rect1.x, rect1.y });
std.debug.print("Rectangle 2: {}x{} at ({d:.1}, {d:.1})\n",
.{ rect2.width, rect2.height, rect2.x, rect2.y });
// Complex struct
const person = Person{
.name = "Alice",
.age = 25,
.height = 175.5,
.is_student = true,
};
std.debug.print("Person info: {s}, {} years old, {d:.1}cm, student: {}\n",
.{ person.name, person.age, person.height, person.is_student });
}Struct Methods
zig
const std = @import("std");
const Circle = struct {
center: Point,
radius: f32,
const Self = @This();
// Constructor
pub fn init(x: f32, y: f32, radius: f32) Self {
return Self{
.center = Point{ .x = x, .y = y },
.radius = radius,
};
}
// Calculate area
pub fn area(self: Self) f32 {
return std.math.pi * self.radius * self.radius;
}
// Calculate circumference
pub fn circumference(self: Self) f32 {
return 2.0 * std.math.pi * self.radius;
}
// Check if point is inside circle
pub fn contains(self: Self, point: Point) bool {
const dx = point.x - self.center.x;
const dy = point.y - self.center.y;
const distance_squared = dx * dx + dy * dy;
return distance_squared <= self.radius * self.radius;
}
// Move center
pub fn move(self: *Self, dx: f32, dy: f32) void {
self.center.x += dx;
self.center.y += dy;
}
// Scale radius
pub fn scale(self: *Self, factor: f32) void {
self.radius *= factor;
}
};
const Point = struct {
x: f32,
y: f32,
pub fn distance(self: Point, other: Point) f32 {
const dx = self.x - other.x;
const dy = self.y - other.y;
return @sqrt(dx * dx + dy * dy);
}
};
pub fn main() void {
// Create circle
var circle = Circle.init(0.0, 0.0, 5.0);
std.debug.print("Center: ({d:.1}, {d:.1}), Radius: {d:.1}\n",
.{ circle.center.x, circle.center.y, circle.radius });
std.debug.print("Area: {d:.2}\n", .{circle.area()});
std.debug.print("Circumference: {d:.2}\n", .{circle.circumference()});
// Test if points are inside circle
const test_points = [_]Point{
Point{ .x = 0.0, .y = 0.0 },
Point{ .x = 3.0, .y = 4.0 },
Point{ .x = 6.0, .y = 0.0 },
};
for (test_points) |point| {
const inside = circle.contains(point);
std.debug.print("Point ({d:.1}, {d:.1}) inside circle: {}\n",
.{ point.x, point.y, inside });
}
// Move and scale circle
circle.move(2.0, 3.0);
circle.scale(1.5);
std.debug.print("After moving and scaling:\n");
std.debug.print("Center: ({d:.1}, {d:.1}), Radius: {d:.1}\n",
.{ circle.center.x, circle.center.y, circle.radius });
}Enum Basics
Simple Enums
zig
const std = @import("std");
// Basic enum
const Color = enum {
Red,
Green,
Blue,
Yellow,
Purple,
// Enum methods
pub fn toString(self: Color) []const u8 {
return switch (self) {
.Red => "Red",
.Green => "Green",
.Blue => "Blue",
.Yellow => "Yellow",
.Purple => "Purple",
};
}
pub fn isWarm(self: Color) bool {
return switch (self) {
.Red, .Yellow => true,
.Green, .Blue, .Purple => false,
};
}
};
// Enum with values
const HttpStatus = enum(u16) {
Ok = 200,
NotFound = 404,
InternalServerError = 500,
BadRequest = 400,
Unauthorized = 401,
pub fn getMessage(self: HttpStatus) []const u8 {
return switch (self) {
.Ok => "Request successful",
.NotFound => "Resource not found",
.InternalServerError => "Internal server error",
.BadRequest => "Bad request format",
.Unauthorized => "Unauthorized access",
};
}
pub fn isError(self: HttpStatus) bool {
return @intFromEnum(self) >= 400;
}
};
pub fn main() void {
// Using basic enum
const colors = [_]Color{ .Red, .Green, .Blue, .Yellow, .Purple };
std.debug.print("Color information:\n");
for (colors) |color| {
std.debug.print(" {s} - Warm: {}\n", .{ color.toString(), color.isWarm() });
}
// Using enum with values
const statuses = [_]HttpStatus{ .Ok, .NotFound, .InternalServerError, .BadRequest };
std.debug.print("\nHTTP Status codes:\n");
for (statuses) |status| {
std.debug.print(" {} - {s} - Error: {}\n",
.{ @intFromEnum(status), status.getMessage(), status.isError() });
}
}Union Types
Tagged Unions
zig
const std = @import("std");
// Tagged union: can store different types of values
const Value = union(enum) {
Integer: i32,
Float: f64,
String: []const u8,
Boolean: bool,
Array: []i32,
pub fn getType(self: Value) []const u8 {
return switch (self) {
.Integer => "Integer",
.Float => "Float",
.String => "String",
.Boolean => "Boolean",
.Array => "Array",
};
}
pub fn print(self: Value) void {
switch (self) {
.Integer => |int| std.debug.print("Integer: {}\n", .{int}),
.Float => |float| std.debug.print("Float: {d:.2}\n", .{float}),
.String => |string| std.debug.print("String: {s}\n", .{string}),
.Boolean => |boolean| std.debug.print("Boolean: {}\n", .{boolean}),
.Array => |array| {
std.debug.print("Array: [");
for (array, 0..) |item, i| {
if (i > 0) std.debug.print(", ");
std.debug.print("{}", .{item});
}
std.debug.print("]\n");
},
}
}
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Value union example
const values = [_]Value{
Value{ .Integer = 42 },
Value{ .Float = 3.14159 },
Value{ .String = "Hello, Zig!" },
Value{ .Boolean = true },
Value{ .Array = &[_]i32{ 1, 2, 3, 4, 5 } },
};
std.debug.print("Value union example:\n");
for (values) |value| {
std.debug.print("Type: {s} - ", .{value.getType()});
value.print();
}
}Generic Structs
Generic Containers
zig
const std = @import("std");
// Generic stack
fn Stack(comptime T: type) type {
return struct {
items: []T,
count: usize,
allocator: std.mem.Allocator,
const Self = @This();
pub fn init(allocator: std.mem.Allocator, capacity: usize) !Self {
const items = try allocator.alloc(T, capacity);
return Self{
.items = items,
.count = 0,
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
self.allocator.free(self.items);
}
pub fn push(self: *Self, item: T) !void {
if (self.count >= self.items.len) {
return error.StackOverflow;
}
self.items[self.count] = item;
self.count += 1;
}
pub fn pop(self: *Self) ?T {
if (self.count == 0) return null;
self.count -= 1;
return self.items[self.count];
}
pub fn peek(self: *const Self) ?T {
if (self.count == 0) return null;
return self.items[self.count - 1];
}
pub fn isEmpty(self: *const Self) bool {
return self.count == 0;
}
pub fn size(self: *const Self) usize {
return self.count;
}
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Integer stack
var int_stack = try Stack(i32).init(allocator, 10);
defer int_stack.deinit();
// Push elements
try int_stack.push(10);
try int_stack.push(20);
try int_stack.push(30);
std.debug.print("Integer stack size: {}\n", .{int_stack.size()});
std.debug.print("Top element: {?}\n", .{int_stack.peek()});
// Pop elements
while (!int_stack.isEmpty()) {
if (int_stack.pop()) |item| {
std.debug.print("Popped: {}\n", .{item});
}
}
}Summary
This chapter covered Zig structs and enums in detail:
- ✅ Struct definition, initialization, and methods
- ✅ Nested structs and complex data organization
- ✅ Basic enum usage and enums with values
- ✅ Union types and tagged unions
- ✅ Optional types with structs
- ✅ Generic struct implementation
- ✅ Best practices and memory optimization
Structs and enums are fundamental tools for building complex data structures and implementing object-oriented designs. Mastering their usage is crucial for writing high-quality Zig programs. In the next chapter, we'll learn about Zig's atomic operations.