Last active
February 16, 2025 15:20
-
-
Save jdmichaud/b75ee234bfa87283a6337e06a3b70767 to your computer and use it in GitHub Desktop.
Zig cheatsheet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| |
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Allocate a slice | |
const slice: []const u8 = try allocator.alloc(u8, 256); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} | |
}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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..])).*; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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] }); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ref: https://zig.guide/standard-library/readers-and-writers/ | |
fn nextLine(reader: anytype, buffer: []u8) !?[]const u8 { | |
var line = (try reader.readUntilDelimiterOrEof( | |
buffer, | |
'\n', | |
)) orelse return null; | |
// trim annoying windows-only carriage return character | |
if (@import("builtin").os.tag == .windows) { | |
return std.mem.trimRight(u8, line, "\r"); | |
} else { | |
return line; | |
} | |
} | |
pub fn main() !void { | |
const stdout = std.io.getStdOut(); | |
const stdin = std.io.getStdIn(); | |
try stdout.writeAll( | |
\\ Enter your name: | |
); | |
var buffer: [100]u8 = undefined; | |
const input = (try nextLine(stdin.reader(), &buffer)).?; | |
try stdout.writer().print( | |
"Your name is: \"{s}\"\n", | |
.{input}, | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you!