softprops/zig-iter
iterators for zig
If you are coming to zig from any variety of other languages (we welcome you) you might be asking the questions like: how can I transform this zig collection?, how can I filter out elements?, and other perfectly valid questions based on what you might be used to from where you are coming from. The answer in zig is "it depends", but you'll likely be using a for loop and allocating a copy of the collection you have on hand.
Let's use a very simple example: doubling the value of an array of elems that you may do something later with. I'll just print it out for simplicity, but you'll likely be doing something more useful.
const elems = [_]i32{ 1, 2, 3 };
// π conjure an allocator for the list below
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// π allocate a new list to hold the data of the transformation, DONT FORGET TO DEALLOCATE IT
var buf = try std.ArrayList(i32).initCapacity(allocator, elems.len);
defer buf.deinit();
for (elems) |elem| {
buf.appendAssumeCapacity(elem * 2);
}
// π capture a ref to the slice of data you want, DONT FORGET TO DEALLOCATE IT
const doubled = try buf.toOwnedSlice();
defer allocator.free(doubled);
// π do something with it
for (doubled) |elem| {
std.debug.print("{d}", .{elem});
}
The simple example above quickly becomes much more complicated as additional transformations and filtering is required.
If you are coming to zig from another language you are probably used to expressing this with something like elems.map(...)
With this library you can almost have that too. Below is an equivalent program but sans required additional allocations and zig's required memory deallocation.
var elems = [_]i32 { 1, 2, 3 };
// π create an interator and apply a transformation
var doubled = iter.from(elems)
.then().map(i32, struct { fn func(n: i32) i32 { return n * 2; } }.func);
// π do something with it
while (doubled.next()) |elem| {
std.debug.print("{d}", .{elem});
}
I say almost because
usingnamespace
facilitate the need for an itermediatory method, we use then()
, to access and chain iterator methods. If zig brings that back in a different form this library's then()
will no longer been nessessary.The following functions create iterators
from(zigType)
- create an iterator for a native zig type, we're expanding the list of types supportedfromFn(returnType, init, func)
- create an iterator from a generator funconce(value)
- create an iterator that only repeats oncerepeat(value)
- create an iterator that repeats a given value indefinitelyThe following methods are available when calling then()
on iterator types
chain(other)
- extends one iterator with anothercycle()
- repeats an iterator indefinitelyfilter(func)
- filters an iterator by testing a func
predicatefold(returnType, init, func)
- reduces an iterator down to a single valuemap(returnType, func)
- transforms an iterator by applying func
skip(n)
- skip the first n
elems of an iteratortake(n)
- take only the first n
elems of an iteratorzip(iter)
- create an iterator yielding a tuple of iterator valueslikely more to come
Note, nothing is allocated behind the scenes. If you do need to take the results and
store the result in an allocatoed type simply do what you would do with any iterator: feed
it values of next()
var elems = [_]i32 { 1, 2, 3 };
var doubled = iter.from(elems)
.then().map(i32, struct { fn func(n: i32) i32 { return n * 2; } }.func);
// now go ahead feed the result into a list
// π conjure an allocator for the list below
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// π allocate a new list to hold the data of the transformation, DONT FORGET TO DEALLOCATE IT
var buf = try std.ArrayList(i32).initCapacity(allocator, elems.len);
defer buf.deinit();
while (doubled.next()) |elem| {
buf.appendAssumeCapacity(elem * 2);
}
// π capture a ref to the slice of data you want, DONT FORGET TO DEALLOCATE IT
const copied = try buf.toOwnedSlice();
defer allocator.free(copied);
For more examples see the examples
directory
Create a new exec project with zig init
. Copy an example from the examples directory into your into src/main.zig
Create a build.zig.zon
file to declare a dependency
.zon short for "zig object notation" files are essentially zig structs.
build.zig.zon
is zigs native package manager convention for where to declare dependencies
Starting in zig 0.12.0, you can use and should prefer
zig fetch --save https://github.com/softprops/zig-iter/archive/refs/tags/v0.1.0.tar.gz
otherwise, to manually add it, do so as follows
.{
.name = "my-app",
.version = "0.1.0",
.dependencies = .{
+ // π declare dep properties
+ .iter = .{
+ // π uri to download
+ .url = "https://github.com/softprops/zig-iter/archive/refs/tags/v0.1.0.tar.gz",
+ // π hash verification
+ .hash = "...",
+ },
},
}
the hash below may vary. you can also depend any tag with
https://github.com/softprops/zig-iter/archive/refs/tags/v{version}.tar.gz
or current main withhttps://github.com/softprops/zig-iter/archive/refs/heads/main/main.tar.gz
. to resolve a hash omit it and let zig tell you the expected value.
Add the following in your build.zig
file
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// π de-reference dep from build.zig.zon`
+ const iter = b.dependency("iter", .{
+ .target = target,
+ .optimize = optimize,
+ }).module("iter");
var exe = b.addExecutable(.{
.name = "your-exe",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
// π add the module to executable
+ exe.root_mode.addImport("iter", iter);
b.installArtifact(exe);
}
Does this look interesting but you're new to zig and feel left out? No problem, zig is young so most us of our new are as well. Here are some resources to help get you up to speed on zig
- softprops 2024