Zig Functions and Structs: Custom Types

Zig Functions and Structs: Custom Types
Zig does not follow the Object-Oriented Programming (OOP) paradigm of "Classes," "Inheritance," or "Private/Public" access modifiers for fields. There are no "Constructors" that run hidden logic when you create an object. Instead, Zig embraces a "Data-Oriented" philosophy where Structs define memory layout and Functions define logic.
This 1,500+ word guide explores the "Explicit Structure" of Zig. We will master the pub (visibility) keyword, learn how to attach Methods via pointer captures, and explore the "Init/Deinit" pattern that allows Zig to manage resources without the overhead of a Garbage Collector or the complexity of C++ Destructors.
1. Structs: The Physical Reality of Memory
A struct in Zig is a named collection of fields. However, Zig offers three different ways to store that data depending on how much control you need over the actual bytes on the CPU.
A. The Standard struct (Zig-Optimized)
In a standard struct, the compiler is allowed to reorder your fields to minimize Padding.
If you put a bool (1 byte) between two u32 (4 bytes), a normal compiler might waste 3 bytes of "Padding" to keep the alignment. Zig will move the bool to the end of the struct automatically to keep your memory footprint as small as possible.
B. The extern struct (C-Compatibility)
If you are passing data to a C library, you use extern struct. This guarantees that the field order remains EXACTLY as you wrote it, matching the C application binary interface (ABI).
C. The packed struct (Hardware Mapping)
This is Zig's "Superpower." A packed struct has Zero Padding. You specify exactly how many bits each field takes.
This is essential for writing Operating System kernels or working with hardware registers where every single bit is a switch that controls a physical device.
2. The Physics of Padding: Struct Layout and the Cache
In high-performance systems, the order of your fields is not just an aesthetic choice; it is a Cache Optimization.
The Cache Line Mirror
- The Concept: CPUs read memory in Cache Lines (usually 64 bytes).
- The Physics: If a struct field crosses a cache line boundary, the CPU must perform two memory reads instead of one, doubling the latency.
- Zig's Logic: For standard
structtypes, Zig uses Field Reordering. It places smaller fields into the gaps created by larger fields (padding). - The Hardware Result: This keeps your struct compact, ensuring that more instances of your type fit into the L1 Data Cache, resulting in massive throughput gains for data-intensive loops.
3. Functions: Precision Execution
Functions in Zig are straightforward, but they have strict rules regarding parameters.
Parameters are Constants
In Zig, function parameters are always immutable (const). You cannot change the value of a parameter passed to a function.
If you need to change a value, you must pass a Pointer (*i32). This forces high-quality code: you always know if a function is going to modify your data just by looking at its signature.
Returning Errors
Almost every professional Zig function returns an "Error Union."
The ! means "Maybe a Connection, Maybe an Error." This ensures that the caller must handle the failure case using try, catch, or if.
3. Methods: Pointer vs. Value Captures
Zig doesn't have "Methods" in the Java sense, but it provides Dot Notation Syntax. If the first argument of a function is a struct type, you can call it from an instance of that struct.
The self vs *self Decision
- Use
self: Vec3for simple calculations. It is thread-safe because it works on a copy. - Use
self: *Vec3for any function that updates the state.
4. The "Init / Deinit" Lifecycle
Because Zig has no "Constructors" or "Garbage Collection," resource management is explicit and beautiful. The community standard is the "Init/Deinit" pattern.
The "Shotgun" Release
The power of this pattern comes when you use defer.
This ensures that your memory is always cleaned up, but the timing is 100% predictable.
5. Dot-Notation Internals: Zero-Cost Methods
Zig provides "Dot-Notation" (e.g., user.init()), but it is critical to understand that this is Syntactic Sugar, not a runtime lookup.
The Dispatch Mirror
- The Process: In Java or Python, calling a method often involves a "Virtual Table" (vtable) lookup at runtime.
- The Physics: In Zig,
user.activate()is converted by the compiler intoUser.activate(&user)at compile-time. - The Result: There is Zero Runtime Overhead. The "Method" is just a standard function, and the "Instance" is just the first parameter. By mastering this, you gain the readability of OOP with the raw speed of C.
6. Anonymous Structs and Tuples
Zig allows you to create structures without naming them first. This is primarily used for returning multiple values or passing arguments to formatting functions.
- Tuples:
const pair = .{ 10, "Hello" };Access viapair[0]. - Anonymous Struct:
const p = .{ .x = 10, .y = 5 };Access viap.x.
These are "Comptime" constructs that allow Zig to have powerful type-reflection without the runtime cost of "Interfaces" or "Reflection APIs" found in Java.
Structs are the "Blueprints" of your system. By mastering the distinction between packed memory and the explicit lifecycle of pointers, you gain the ability to build data structures that are both incredibly efficient and perfectly readable. You graduate from "Managing variables" to "Architecting Types."
Phase 7: Type Architecture Checklist
- Audit your data structures: Convert "Manager" classes into simple Structs + Functions.
- Implement the Init/Deinit pattern for every struct that handles external resources (RAM, Files, Sockets).
- Optimize your memory footprint: Use
@sizeOfto verify if Zig's Field Reordering is saving you space. - Use
extern structfor any data that must be shared with a C library (ABI-stability). - Leverage Anonymous Structs for clean, ad-hoc grouping of return values in internal logic.
Read next: Error Handling: The Logic of Failure →
Part of the Zig Mastery Course — engineering the types.
