Skip to content

Commit 2ae37ca

Browse files
committed
Update README and add MultiArrayList example
1 parent df47dc3 commit 2ae37ca

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
## About
99

10-
This repository contains examples of common design patterns found in Zig's standard library and many community projects. I hope that these examples will help people new to the language gain an understanding of the many ways one can structure their programs in Zig, as well as why Zig does not prescribe a certain way to do "interfaces."
10+
This repository contains examples of common design patterns found in Zig's standard library and many community projects.
11+
12+
Note that copying (one of) these patterns verbatim into your project may not be useful; it's important to weigh the pros and cons of different patterns. If you come from another language, especially a non-systems one, you'll often find that the more unfamiliar ideas featured here may be more useful.
13+
14+
For example, I've seldom used interface-like patterns when designing systems in Zig, despite using such patterns dozens of times a day at work writing TypeScript or Go.
1115

1216
## Examples
1317

@@ -17,6 +21,7 @@ All examples are annotated with comments including recommendations regarding whe
1721
| ----- | --- |
1822
| [`@fieldParentPtr`](src/field_parent_ptr.zig) | `zig build field_parent_ptr` |
1923
| [Inline Switch](src/inline_switch.zig) | `zig build inline_switch` |
24+
| [MultiArrayList](src/multi_array_list.zig) | `zig build multi_array_list` |
2025
| [Type Function](src/type_function.zig) | `zig build type_function` |
2126
| [Virtual Table](src/vtable.zig) | `zig build vtable` |
2227

build.zig

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const std = @import("std");
33
pub const examples = .{
44
.{ "field_parent_ptr", "src/field_parent_ptr.zig" },
55
.{ "inline_switch", "src/inline_switch.zig" },
6+
.{ "multi_array_list", "src/multi_array_list.zig" },
67
.{ "type_function", "src/type_function.zig" },
78
.{ "vtable", "src/vtable.zig" },
89
};

src/multi_array_list.zig

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//! Let's say that we want to store a list of `struct {a: u8, b: u16}`s.
2+
//!
3+
//! We could use a normal ArrayList; we'd call this an Array of Structs (AoS) design,
4+
//! though in Zig "Slice of Structs" would be a more correct name, because we'd be
5+
//! storing a list where each entry in the list stores the whole struct contiguously.
6+
//!
7+
//! Alternatively, we could use MultiArrayList, where the list is split into
8+
//! `Struct.fields.len` segments, each storing all the values of a certain field
9+
//! contiguously. We'd call this a Struct of Arrays (SoA) design, which is
10+
//! also a misnomer when applied to Zig. :^)
11+
//!
12+
//! This is a little odd to wrap one's head around at first, so here's a diagram
13+
//! of how each design looks in memory with the example struct mentioned above:
14+
//!
15+
//! ArrayList (AoS): { {aaaaaaaabbbbbbbbbbbbbbbb} {aaaaaaaabbbbbbbbbbbbbbbb} {aaaaaaaabbbbbbbbbbbbbbbb} }
16+
//! MultiArrayList (SoA): { {aaaaaaaa} {aaaaaaaa} {aaaaaaaa} {bbbbbbbbbbbbbbbb} {bbbbbbbbbbbbbbbb} {bbbbbbbbbbbbbbbb} }
17+
//!
18+
//! Both example lists show three entries and each letter represents a bit of memory for
19+
//! that field. Note that this diagram is simplified, and that we do not represent
20+
//! any padding that may be introduced by the compiler.
21+
//!
22+
//! When to use:
23+
//! - You only access certain fields most of the time, and loading
24+
//! the entire struct would be a waste
25+
//! - You want to help the compiler autovectorize / perform some
26+
//! manual vectorization using Zig's SIMD features
27+
//!
28+
//! When not to use:
29+
//! - If you're always accessing every field in your list entries,
30+
//! then you won't find much use in MultiArrayLists
31+
32+
const std = @import("std");
33+
34+
pub const Player = struct {
35+
name: []const u8,
36+
health: u16,
37+
};
38+
39+
const PlayerList = std.MultiArrayList(Player);
40+
41+
test {
42+
const allocator = std.testing.allocator;
43+
44+
var players = PlayerList{};
45+
defer players.deinit(allocator);
46+
47+
try players.append(allocator, .{ .name = "Auguste", .health = 100 });
48+
try players.append(allocator, .{ .name = "Techatrix", .health = 125 });
49+
try players.append(allocator, .{ .name = "ZLS Premium User", .health = 50_000 });
50+
51+
// We're about to call `.items(...)` a bunch,
52+
// so we can save some performance by
53+
// calling `.slice()` and calling `.items(...)`
54+
// on that instead.
55+
var slice = players.slice();
56+
57+
// I just want to increase every player's health!
58+
// This pattern allows LLVM to easily autovectorize
59+
// this modification code; I challenge you to look
60+
// at this in Compiler Explorer! :)
61+
for (slice.items(.health)) |*health| {
62+
health.* += 10;
63+
}
64+
65+
// Let's access both fields and see where our players'
66+
// health is at now!
67+
for (slice.items(.name), slice.items(.health)) |name, health| {
68+
std.debug.print("{s} has {d} health!\n", .{ name, health });
69+
}
70+
}

0 commit comments

Comments
 (0)