Zig Structs
Structs are a pervasive composite type and is core to zig. Importing the standard library const std = @import("std");
is
essentially a struct. Let’s learn how we declare them, create instances, add functions and methods, and finally, how we
leverage the @This()
built-in function to give it super powers.
Declaring Structs
In zig, structs are anonymous. Let’s create a new type Vector
. Here we name the struct by passing it into a
variable named Vector
. The name becomes the type.
const Vector = struct {
x: i32,
y: i32,
};
Instantiating Structs
When declaring an instance of a struct, all fields must be assigned a value. Let’s create a new Vector
and print
it to the terminal.
+ const std = @import("std");
+ const print = std.debug.print;
const Vector = struct {
x: i32,
y: i32,
};
+ pub fn main() void {
+ const v = Vector{ .x = 50, .y = 50 };
+ print("{}\n", .{v}); // main.Vector{ .x = 50, .y = 50 }
+ }
You can set default values in the struct definition. We’ll set both x
and y
to 0 by default.
const std = @import("std");
const print = std.debug.print;
const Vector = struct {
- x: i32,
- x: i32,
+ x: i32 = 0,
+ y: i32 = 0,
};
pub fn main() void {
const v = Vector{ .x = 50, .y = 50 };
print("{}\n", .{v}); // main.Vector{ .x = 50, .y = 50 }
}
This will change how you’re allowed to declare an instance of the struct. You only need to assign a value to a field if it’s unassigned
in the definition. Assigning a value to a field that has a default value in the definition is optional. Here we skip setting a value
for y
, which defaults to 0.
const std = @import("std");
const print = std.debug.print;
const Vector = struct {
x: i32 = 0,
y: i32 = 0,
};
pub fn main() void {
- const v = Vector{ .x = 50, .y = 50 };
+ const v = Vector{ .x = 50 };
print("{}\n", .{v}); // main.Vector{ .x = 50, .y = 0 }
}
Struct Functions
You can declare functions inside of a struct definition. The named struct type can be used in function declarations and definitions.
Let’s declare a new function named init()
, which returns an anonymous struct. Zig will coerce the anonymous struct into a Vector
type.
const std = @import("std");
const print = std.debug.print;
const Vector = struct {
x: i32 = 0,
y: i32 = 0,
+ fn init(x: i32, y: i32) Vector {
+ return .{ .x = x, .y = y }; // anonymous struct is coerced into the return type
+ }
};
pub fn main() void {
- const v = Vector{ .x = 50 };
+ const v = Vector.init(50, 50);
print("{}\n", .{v}); // main.Vector{ .x = 50, .y = 50 }
}
Struct Methods
You can also declare methods inside of a struct definition. The named struct type can be used in method declaration and definitions. When using struct types as a parameter to a method, by default, zig will pass the instance of the struct itself in the first parameter.
Use self
in a method declaration and definition to indicate how the method is expected to be used. Naming the first parameter of a
struct method self
indicates to the caller that the instance of the type will be the first parameter.
Here we declare a method of Vector
named sum()
, which adds a Vector
to a Vector
instance and returns a new Vector
.
const std = @import("std");
const print = std.debug.print;
const Vector = struct {
x: i32,
y: i32,
fn init(x: i32, y: i32) Vector {
return .{ .x = x, .y = y };
}
+ fn sum(self: Vector, v: Vector) Vector {
+ return .{
+ .x = self.x + v.x,
+ .y = self.y + v.y,
+ };
+ }
};
pub fn main() void {
- const v = Vector.init(50, 50);
- print("{}\n", .{v}); // main.Vector{ .x = 50, .y = 50 }
+ const v1 = Vector{ .x = 50, .y = 50 };
+ const v2 = Vector{ .x = 20, .y = 20 };
+ const v3 = v1.sum(v2); // Vector `v1` is used as the first parameter
+ print("{}\n", .{v3}); // main.Vector{ .x = 70, .y = 70 }
}
This()
Files in zig are namedspaced struct declarations and definitions. Let’s move our Vector
type to a separate file named vector.zig.
@This()
is a special built-in function. It returns the type of the struct that contains it. In this example @This()
is namespaced to Vector
and contains the file vector.zig. We must now update any function we want to call outside of this scope to public with pub
.
// vector.zig
x: i32,
y: i32,
const Vector = @This();
pub fn init(x: i32, y: i32) Vector {
return .{ .x = x, .y = y };
}
pub fn sum(self: Vector, v: Vector) Vector {
return .{
.x = self.x + v.x,
.y = self.y + v.y,
}
}
Now we can import this into a project and use it.
// main.zig
const std = @import("std");
const print = std.debug.print;
const Vector = @import("vector.zig");
pub fn main() void {
const v1 = Vector.init(25, 25);
const v2 = Vector.init(50, 50);
const v3 = v1.sum(v2);
print("{}\n", .{v3}); // Vector{ .x = 75, .y = 75 }
}