#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
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
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
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
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
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.