Lesson 7 / 25
Structs, Methods and Namespaces
Define structs with default values, methods and declarations.
Structs are also namespaces
A struct groups fields: const Item = struct { sku: []const u8, qty: u32 = 1 };, with optional default values. Instances are created with struct literals (.{ .sku = "pen", .qty = 2 }), where the leading dot lets the compiler infer the type from context. Functions declared inside a struct are methods when their first parameter is the struct type or a pointer to it, and can be called with dot syntax: item.total() is sugar for Item.total(item). Use self: Item (a copy, read-only) or self: *Item (to modify). @This() refers to the enclosing type, useful in generic code. Structs also hold declarations (constants, nested types, functions), so a struct doubles as a namespace, and every Zig source file is itself a struct whose public (pub) declarations can be imported with @import. A common convention is an init function returning a configured instance and a deinit method that releases resources. Anonymous struct literals and tuples (.{ 1, "two" }) are used for format arguments and multiple return values.
A struct with fields and declarations
Fields hold per-instance data; declarations such as methods and constants belong to the type.
An invoice struct with methods
Default values, methods with self and a namespaced constant.
const std = @import("std");
const LineItem = struct {
sku: []const u8,
price_paise: u32,
qty: u32 = 1, // default value
fn total(self: LineItem) u64 {
return @as(u64, self.price_paise) * self.qty;
}
};
const Invoice = struct {
number: u32,
items: []const LineItem,
paid: bool = false,
pub const gst_percent: u64 = 18; // a declaration, shared by all invoices
pub fn subtotal(self: Invoice) u64 {
var sum: u64 = 0;
for (self.items) |item| sum += item.total();
return sum;
}
pub fn grandTotal(self: Invoice) u64 {
const s = self.subtotal();
return s + (s * gst_percent + 50) / 100; // round to nearest paisa
}
pub fn markPaid(self: *Invoice) void { // pointer receiver: modifies the invoice
self.paid = true;
}
};
pub fn main() void {
const items = [_]LineItem{
.{ .sku = "pen", .price_paise = 4_950, .qty = 2 },
.{ .sku = "ink", .price_paise = 19_900 }, // qty defaults to 1
};
var inv = Invoice{ .number = 7, .items = &items };
inv.markPaid();
std.debug.print("INV-{d}: subtotal {d}, total {d}, paid {}\n", .{ inv.number, inv.subtotal(), inv.grandTotal(), inv.paid });
}Files are structs
Because each file is a struct, const invoice = @import("invoice.zig"); gives you a namespace whose pub declarations you can use as invoice.Invoice or invoice.parse. Non-pub declarations stay private to the file.
Quick check: What does `inv.markPaid()` desugar to when markPaid takes `self: *Invoice`?
- Invoice.markPaid(&inv), passing a pointer
- Invoice.markPaid(inv) with a copy
- A virtual method call
- It is a compile error
Answer
Invoice.markPaid(&inv), passing a pointer — Method call syntax passes the address automatically when the parameter is a pointer.