Skip to content

Instantly share code, notes, and snippets.

@jdmichaud
Last active February 16, 2025 15:20
Show Gist options
  • Save jdmichaud/b75ee234bfa87283a6337e06a3b70767 to your computer and use it in GitHub Desktop.
Save jdmichaud/b75ee234bfa87283a6337e06a3b70767 to your computer and use it in GitHub Desktop.
Zig cheatsheet
https://ziglang.org/documentation/master/#Pointers
*T - single-item pointer to exactly one item.
Supports deref syntax: ptr.*
[*]T - pointer to unknown number of items. (eq. of *T in C)
Supports index syntax: ptr[i]
Supports slice syntax: ptr[start..end]
Supports pointer arithmetic: ptr + x, ptr - x
T must have a known size, which means that it cannot be anyopaque or any other opaque type.
*[N]T - pointer to N items, same as single-item pointer to an array.
Supports index syntax: array_ptr[i]
Supports slice syntax: array_ptr[start..end]
Supports len property: array_ptr.len
[]T - pointer to runtime-known number of items.
Supports index syntax: slice[i]
Supports slice syntax: slice[start..end]
Supports len property: slice.len
https://github.com/ziglang/zig/wiki/Zig-Newcomer-Programming-FAQs#what-is-a-t
What's inside the `[.]T` is the number of items.
`[n]T` is an array with a compile time known number of items.
`[*]T` means the number is indeterminate (like a kleene star). It's a pointer to an array of unknown size.
`[]T` is a slice, meaning it's a struct with a `[*]` and a size_t for the runtime known size of the array.
```c
const arr = [_]u8{ 5, 1, 8, 4, 5, 6 };
std.log.debug("arr: {}", .{ @TypeOf(arr) });
std.log.debug("&arr: {}", .{ @TypeOf(&arr) });
const sli: []const u8 = &arr;
std.log.debug("sli: {}", .{ @TypeOf(sli) });
std.log.debug("sli[0..]: {}", .{ @TypeOf(sli[0..]) });
std.log.debug("sli[0..].*: {}", .{ @TypeOf(sli[0..].*) });
var known_at_runtime_zero: usize = 3;
_ = &known_at_runtime_zero;
std.log.debug("sli[known_at_runtime_zero..][0..3].*: {}", .{ @TypeOf(sli[known_at_runtime_zero..][0..3].*) });
```
This will give:
```
arr: [6]u8
&arr: *const [6]u8
sli: []const u8
sli[0..]: *const [6]u8
sli[0..].*: [6]u8
```
Zig automatically converts &arr which is a pointer on the static array to a slice.
A slice is a (sort of) struct containing a pointer and a size (known at compile time here).
Then using `[0..]` we extract the pointer from the slice and with `.*` we dereference it back to a static array.
When the start position is known at runtime, we can still extract comptime known length slice by double slicing.
First from the runtime start position then from 0 to a static value. This gets us a comptime known array.
// Allocate a slice
const slice: []const u8 = try allocator.alloc(u8, 256);
// A very simple command line argument parser.
//
// The purpose is to be simple, modifiable and easily embeddable. Just
// copy-paste is into your project and that's it.
//
// Usage:
// ```zig
// const args = try std.process.argsAlloc(allocator);
// defer std.process.argsFree(allocator, args);
//
// const parsedArgs = clap.parser(clap.ArgDescriptor{
// .name = "qvm",
// .description = "A QuakeC virtual machine",
// .withHelp = true,
// .version = "0.1.0",
// .expectArgs = &[_][]const u8{ "datfile" },
// .options = &[_]clap.OptionDescription{ .{
// .short = "t",
// .long = "trace",
// .help = "Enable tracing of instructions",
// }, .{
// .short = "e",
// .long = "verbose",
// .help = "Display additional information about the VM",
// }, .{
// .short = "m",
// .long = "memory-size",
// .arg = .{ .name = "memory", .type = []const u8 },
// .help = "Amount of memory to allocate for the VM (-m 12, -m 64K, -m 1M)",
// }, .{
// .short = "j",
// .long = "jump-to",
// .arg = .{ .name = "function", .type = []const u8 },
// .help = "Jump to function on startup",
// }, .{
// .short = "b",
// .long = "bsp-file",
// .arg = .{ .name = "bspfile", .type = []const u8 },
// .help = "Load a BSP file",
// }, .{
// .short = "r",
// .long = "run",
// .help = "Run the event loop (triggering the nextthink timers)",
// } },
// }).parse(args);
//
// const filepath = parsedArgs.arguments.items[0];
// const memsize = if (args.getOption([]const u8, "memory-size")) |memsizeArg| blkinner: {
// const lastChar = memsizeArg[memsizeArg.len - 1];
// break :blkinner switch (lastChar) {
// 'k', 'K' => try std.fmt.parseInt(usize, memsizeArg[0..memsizeArg.len - 1], 10) * 1024,
// 'm', 'M' => try std.fmt.parseInt(usize, memsizeArg[0..memsizeArg.len - 1], 10) * 1024 * 1024,
// else => try std.fmt.parseInt(usize, memsizeArg, 10),
// };
// } else 1024 * 1024 * 1; // 1Mb by default;
// // Create the VM
// var vm = try VM.init(allocator, .{
// .entries = null,
// }, .{
// .trace = parsedArgs.getSwitch("trace"),
// .memsize = memsize,
// .verbose = parsedArgs.getSwitch("verbose"),
// });
// defer vm.deinit();
// ```
// Running with the `--help` option will show:
// ```
// qvm (0.1.0) A QuakeC virtual machine
// Usage: qvm [OPTIONS] datfile
//
// Options:
// -t,--trace Enable tracing of instructions
// -e,--verbose Display additional information about the VM
// -m,--memory-size memory
// Amount of memory to allocate for the VM (-m 12, -m 64K, -m 1M)
// -j,--jump-to function
// Jump to function on startup
// -b,--bsp-file bspfile
// Load a BSP file
// -r,--run Run the event loop (triggering the nextthink timers)
//
// ```
const std = @import("std");
const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer();
pub const OptionDescription = struct {
short: ?[]const u8,
long: []const u8,
arg: ?struct { name: []const u8, type: type } = null,
help: []const u8,
};
pub const ArgDescriptor = struct {
bufferSize: usize = 1024,
name: []const u8,
description: ?[]const u8 = null,
withHelp: bool = true,
version: ?[]const u8 = null,
expectArgs: []const []const u8 = &[_][]const u8{},
options: []const OptionDescription,
};
pub fn findOption(comptime T: type, value: anytype, argsInfo: std.builtin.Type.Struct,
name: []const u8) ?type {
inline for (argsInfo.fields) |field| {
if (std.mem.eql(u8, field.name, name) and field.type == T) {
return @field(value, field.name);
}
}
return null;
}
pub fn printUsage(allocator: std.mem.Allocator, argsDescriptor: ArgDescriptor) void {
stdout.print("Usage: {s}{s}{s}\n", .{
argsDescriptor.name,
if (argsDescriptor.options.len > 0) " [OPTIONS]" else "",
if (argsDescriptor.expectArgs.len > 0) blk: {
const argsStr = std.mem.join(allocator, " ", argsDescriptor.expectArgs)
catch @panic("increase fixed buffer size");
break :blk std.fmt.allocPrint(allocator, " {s}", .{ argsStr })
catch @panic("increase fixed buffer size");
} else "",
}) catch unreachable;
}
pub fn printHelp(allocator: std.mem.Allocator, argsDescriptor: ArgDescriptor) void {
stdout.print("{s}{s} {s}\n", .{
argsDescriptor.name,
if (argsDescriptor.version) |version| " (" ++ version ++ ")" else "",
argsDescriptor.description orelse "",
}) catch unreachable;
stdout.print("\n", .{}) catch unreachable;
printUsage(allocator, argsDescriptor);
stdout.print("\nOptions:\n", .{}) catch unreachable;
inline for (argsDescriptor.options) |option| {
var buffer: [argsDescriptor.bufferSize]u8 = undefined;
const printed = std.fmt.bufPrint(&buffer, " {s}{s}{s}", .{
if (option.short) |short| "-" ++ short ++ "," else " ",
"--" ++ option.long,
if (option.arg) |arg| " " ++ arg.name else "",
}) catch @panic("increase fixed buffer size");
if (printed.len > 23) {
stdout.print("{s}\n {s}\n", .{ printed, option.help })
catch unreachable;
} else {
stdout.print("{s: <24} {s}\n", .{ printed, option.help }) catch unreachable;
}
}
}
pub const Args = struct {
const Self = @This();
switchMap: std.StringHashMap(bool),
optionMap: std.StringHashMap([]const u8),
arguments: std.ArrayList([]const u8),
pub fn getSwitch(self: Self, name: []const u8) bool {
return self.switchMap.get(name) orelse false;
}
pub fn getOption(self: Self, comptime T: type, name: []const u8) ?T {
return self.optionMap.get(name);
}
};
pub fn parser(argsDescriptor: ArgDescriptor) type {
return struct {
var buffer: [argsDescriptor.bufferSize]u8 = undefined;
var fba = std.heap.FixedBufferAllocator.init(&buffer);
const allocator = fba.allocator();
var argsStore = Args{
.switchMap = std.StringHashMap(bool).init(allocator),
.optionMap = std.StringHashMap([]const u8).init(allocator),
.arguments = std.ArrayList([]const u8).init(allocator),
};
pub fn parse(args: [][:0]u8) Args {
if (argsDescriptor.withHelp) {
// Look for help and print it
for (args) |arg| {
if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) {
printHelp(allocator, argsDescriptor);
std.posix.exit(0);
}
}
var i: u16 = 1;
while (i < args.len) {
const arg = args[i];
if (arg[0] == '-') {
// Handle option in the block. i might be incremented additionally
// it the option expects an argument.
inline for (argsDescriptor.options) |option| {
if ((option.short != null and std.mem.eql(u8, arg[1..], option.short.?)) or
std.mem.eql(u8, arg[2..], option.long)) {
argsStore.switchMap.put(option.long, true)
catch @panic("increase fixed buffer size");
if (option.arg) |optionArg| {
_ = optionArg;
// We have an argument to the option
if (i > args.len - 1 or args[i + 1][0] == '-') {
// Missing argument
stderr.print("error: option {s} expected an argument\n", .{ arg })
catch unreachable;
printUsage(allocator, argsDescriptor);
}
argsStore.optionMap.put(option.long, args[i + 1])
catch @panic("increase fixed buffer size");
i += 1;
}
break;
}
} else {
// An option was provided but not described.
stderr.print("error: unknown option {s}\n", .{ arg }) catch unreachable;
printUsage(allocator, argsDescriptor);
std.posix.exit(1);
}
} else {
// Here are the argument to the program.
argsStore.arguments.append(args[i]) catch unreachable;
}
i += 1;
}
}
if (argsStore.arguments.items.len != argsDescriptor.expectArgs.len) {
stderr.print("error: incorrect number of arguments. Expected {} arguments, {} given.\n", .{
argsDescriptor.expectArgs.len, argsStore.arguments.items.len,
}) catch unreachable;
printUsage(allocator, argsDescriptor);
std.posix.exit(1);
}
return argsStore;
}
};
}
const std = @import("std");
pub fn main() !void {
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = general_purpose_allocator.allocator();
const args = try std.process.argsAlloc(allocator);
defer std.process.argsFree(allocator, args);
const stdout = std.io.getStdOut().writer();
if (args.len != 1 and (std.mem.eql(u8, args[1], "--help") or std.mem.eql(u8, args[1], "-h"))) {
try stdout.print("{s} is a nice tool\n\n", .{ args[0] });
try stdout.print("usage:\n", .{});
try stdout.print(" {s} - Do something\n", .{ args[0] });
try stdout.print(" {s} parameter - Do something with a parameter\n", .{ args[0] });
return;
}
}
// Convert a slice of type A to a slice of type B
const src: []const u8 = ...;
const dest: []const u32 = @as(*const []const u32, @ptrCast(&src[0..])).*;
// Create a folder
const cwd = std.fs.cwd();
try cwd.makeDir("folder_name");
// Iterate over directory content...
var iter_dir = try std.fs.cwd().openIterableDir(
"folder_name",
.{},
);
// ...and count the number of files
var file_count: usize = 0;
var iter = iter_dir.iterate();
while (try iter.next()) |entry| {
if (entry.kind == .file) file_count += 1;
}
// reference: https://zig.guide/standard-library/filesystem/
// Create a file
const file = try std.fs.cwd().createFile(
"somefile.txt",
.{ .read = true },
);
defer file.close();
// Get file stats
const stat = try file.stat();
try expect(stat.size == 0);
try expect(stat.kind == .file);
try expect(stat.ctime <= std.time.nanoTimestamp());
try expect(stat.mtime <= std.time.nanoTimestamp());
try expect(stat.atime <= std.time.nanoTimestamp());
// Read file
const contents = try file.reader().readAllAlloc(
test_allocator,
256, // read 256 characters
);
defer test_allocator.free(contents);
// Write to file
const file = try std.fs.cwd().createFile(
"someotherfile.txt",
.{ .read = true },
);
defer file.close();
try file.writeAll("Some content");
const std = @import("std");
const pid = 666;
var buffer: [256]u8 = undefined;
// maps_file contains the slice of the printed string
const maps_file = try std.fmt.bufPrint(&buffer, "/proc/{}/maps", .{ pid });
const PakHeader = extern struct {
magic: [4]u8,
offset: u32,
size: u32,
};
const pak: []const u8 = ...;
const pakHeader: *const PakHeader = @ptrCast(&pak[0]);
try stdout.print("magic: {s}\n", .{ pakHeader.magic });
try stdout.print("offset: {}\n", .{ pakHeader.offset });
try stdout.print("size: {}\n", .{ pakHeader.size });
const std = @import("std");
fn load(pathname: []const u8) ![]align(4096) const u8 {
var file = try std.fs.cwd().openFile(pathname, .{});
defer file.close();
const size = try file.getEndPos();
const buffer = try std.posix.mmap(
null,
size,
std.posix.PROT.READ,
.{ .TYPE = .SHARED },
file.handle,
0,
);
errdefer std.posix.munmap(buffer);
return buffer;
}
// import ctypes
//
// class Quux(Structure):
// _pack_ = 1 # Necessary if your structure in align(1) in zig
// _fields_ = [
// ("field", c_uint32),
// ]
//
// lib = ctypes.CDLL("./libpython.so")
// # for ctypes everything is a i32, so this is necessary
// # to pass around 64 bits pointers
// lib.foo.restype = ctypes.c_void_p
// f = lib.foo()
// lib.bar.argtypes = (ctypes.c_void_p,)
// lib.bar(f)
//
// Build with: `zig build-lib python.zig -dynamic`
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = general_purpose_allocator.allocator();
// This function is extern and can be access directly by python
const Quux = extern struct {
field: u32,
};
// This struct is not extern and cannot be accessed directly.
// Use pointers as handlers.
const Foo = struct {
quuxes: []const Quux,
pub fn init() Foo {
const quuxes = &[_]Quux{ Quux{ .field = 42 } };
return Foo{
.quuxes = quuxes,
};
}
};
// This function is exported and accessible by python using ctypes
export fn foo() *Foo {
var f: *Foo = allocator.create(Foo);
f.* = Foo.init();
return &f;
}
// This function is exported and accessible by python using ctypes
export fn bar(f: *Foo) void {
std.log.debug("f.quuxes {any}", .{ f.quuxes[0] });
}
# From https://cookbook.ziglang.cc/01-01-read-file-line-by-line.html
const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const file = try std.fs.cwd().openFile("tests/zig-zen.txt", .{});
defer file.close();
// Wrap the file reader in a buffered reader.
// Since it's usually faster to read a bunch of bytes at once.
var buf_reader = std.io.bufferedReader(file.reader());
const reader = buf_reader.reader();
var line = std.ArrayList(u8).init(allocator);
defer line.deinit();
const writer = line.writer();
while (reader.streamUntilDelimiter(writer, '\n', null)) {
// Clear the line so we can reuse it.
defer line.clearRetainingCapacity();
std.debug.print("{s}\n", .{ line.items });
} else |err| switch (err) {
error.EndOfStream => {}, // end of file
else => return err, // Propagate error
}
}
@trojanfoe
Copy link

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment