Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
zig-version: [0.14.1]
zig-version: [0.15.1]
steps:
- uses: actions/checkout@v4
- uses: mlugg/setup-zig@v2
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

[Zig cookbook](https://github.com/zigcc/zig-cookbook) is a collection of simple Zig programs that demonstrate good practices to accomplish common programming tasks.

> - Main branch tracks Zig 0.14.0 and master, and are tested on Linux and macOS via GitHub actions.
> - Main branch tracks Zig 0.15.x, and is tested on Linux and macOS via GitHub actions.
> - Earlier Zig support could be found in [other branches](https://github.com/zigcc/zig-cookbook/branches).

# How to use
Expand Down
29 changes: 5 additions & 24 deletions assets/src/01-01.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,17 @@ const fs = std.fs;
const print = std.debug.print;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();

const file = try 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();
var file_buffer: [4096]u8 = undefined;
var reader = file.reader(&file_buffer);
var line_no: usize = 0;
while (reader.streamUntilDelimiter(writer, '\n', null)) {
// Clear the line so we can reuse it.
defer line.clearRetainingCapacity();
while (reader.interface.takeDelimiterExclusive('\n')) |line| {
line_no += 1;

print("{d}--{s}\n", .{ line_no, line.items });
print("{d}--{s}\n", .{ line_no, line });
} else |err| switch (err) {
error.EndOfStream => { // end of file
if (line.items.len > 0) {
line_no += 1;
print("{d}--{s}\n", .{ line_no, line.items });
}
},
error.EndOfStream => {}, // Normal termination
else => return err, // Propagate error
}

Expand Down
4 changes: 2 additions & 2 deletions assets/src/01-02.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ pub fn main() !void {
// Before mmap, we need to ensure file isn't empty
try file.setEndPos(content_to_write.len);

const md = try file.metadata();
try std.testing.expectEqual(md.size(), content_to_write.len);
const md = try file.stat();
try std.testing.expectEqual(md.size, content_to_write.len);

const ptr = try std.posix.mmap(
null,
Expand Down
16 changes: 8 additions & 8 deletions assets/src/02-01.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ fn sha256_digest(
file: fs.File,
) ![Sha256.digest_length]u8 {
var sha256 = Sha256.init(.{});
const rdr = file.reader();

var buf: [BUF_SIZE]u8 = undefined;
var n = try rdr.read(&buf);
var file_buf: [BUF_SIZE]u8 = undefined;
var reader = file.reader(&file_buf);
var read_buf: [BUF_SIZE]u8 = undefined;
var n = try reader.interface.readSliceShort(&read_buf);
while (n != 0) {
sha256.update(buf[0..n]);
n = try rdr.read(&buf);
sha256.update(read_buf[0..n]);
n = try reader.interface.readSliceShort(&read_buf);
}

return sha256.finalResult();
Expand All @@ -32,8 +32,8 @@ pub fn main() !void {
const digest = try sha256_digest(file);
const hex_digest = try std.fmt.allocPrint(
allocator,
"{s}",
.{std.fmt.fmtSliceHexLower(&digest)},
"{x}",
.{&digest},
);
defer allocator.free(hex_digest);

Expand Down
2 changes: 1 addition & 1 deletion assets/src/03-01.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const print = std.debug.print;

fn expensive_function() void {
// sleep 500ms
time.sleep(time.ns_per_ms * 500);
std.Thread.sleep(time.ns_per_ms * 500);
}

pub fn main() !void {
Expand Down
40 changes: 27 additions & 13 deletions assets/src/04-01.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Start a TCP server at an unused port.
//! Start an echo TCP server at an unused port.
//!
//! Test with
//! echo "hello zig" | nc localhost <port>
Expand All @@ -8,10 +8,6 @@ const net = std.net;
const print = std.debug.print;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();

const loopback = try net.Ip4Address.parse("127.0.0.1", 0);
const localhost = net.Address{ .in = loopback };
var server = try localhost.listen(.{
Expand All @@ -22,13 +18,31 @@ pub fn main() !void {
const addr = server.listen_address;
print("Listening on {}, access this port to end the program\n", .{addr.getPort()});

var client = try server.accept();
defer client.stream.close();

print("Connection received! {} is sending data.\n", .{client.address});

const message = try client.stream.reader().readAllAlloc(allocator, 1024);
defer allocator.free(message);
const client = try server.accept();
// In real world, you'd want to handle multiple clients, probably in separate threads.
try handleClient(client);
}

print("{} says {s}\n", .{ client.address, message });
fn handleClient(client: net.Server.Connection) !void {
print("Accepted connection from {f}\n", .{client.address});
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format specifier {f} is for floating point numbers, but client.address is a network address. This should be {any} to properly format the address.

Copilot uses AI. Check for mistakes.

defer client.stream.close();
var stream_buf: [1024]u8 = undefined;
var reader = client.stream.reader(&stream_buf);
// Here we echo back what we read directly, so the writer buffer is empty
var writer = client.stream.writer(&.{});

while (true) {
print("Waiting for data from {f}...\n", .{client.address});
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format specifier {f} is for floating point numbers, but client.address is a network address. This should be {any} to properly format the address.

Copilot uses AI. Check for mistakes.

const msg = reader.interface().takeDelimiterInclusive('\n') catch |err| {
if (err == error.EndOfStream) {
print("{f} closed the connection\n", .{client.address});
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format specifier {f} is for floating point numbers, but client.address is a network address. This should be {any} to properly format the address.

Suggested change
print("{f} closed the connection\n", .{client.address});
print("{any} closed the connection\n", .{client.address});

Copilot uses AI. Check for mistakes.

return;
} else {
return err;
}
};
print("{f} says {s}", .{ client.address, msg });
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format specifier {f} is for floating point numbers, but client.address is a network address. This should be {any} to properly format the address.

Copilot uses AI. Check for mistakes.

try writer.interface.writeAll(msg);
// No need to flush, as writer buffer is empty
}
}
12 changes: 6 additions & 6 deletions assets/src/04-02.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ pub fn main() !void {
// Connect to peer
const stream = try net.tcpConnectToAddress(peer);
defer stream.close();
print("Connecting to {}\n", .{peer});
print("Connecting to {f}\n", .{peer});
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format specifier {f} is for floating point numbers, but peer is a network address. This should be {any} to properly format the address.

Suggested change
print("Connecting to {f}\n", .{peer});
print("Connecting to {any}\n", .{peer});

Copilot uses AI. Check for mistakes.


// Sending data to peer
const data = "hello zig";
var writer = stream.writer();
const size = try writer.write(data);
print("Sending '{s}' to peer, total written: {d} bytes\n", .{ data, size });
// Or just using `writer.writeAll`
// try writer.writeAll("hello zig");
var buffer: [1024]u8 = undefined;
var writer = stream.writer(buffer[0..]);
try writer.interface.writeAll(data);
try writer.interface.flush();
print("Sending '{s}' to peer, total written: {d} bytes\n", .{ data, data.len });
}
2 changes: 1 addition & 1 deletion assets/src/04-03.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub fn main() !void {

var buf: [1024]u8 = undefined;

print("Listen on {any}...\n", .{addr});
print("Listen on {f}\n", .{addr});
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format specifier {f} is for floating point numbers, but addr is a network address. This should be {any} to properly format the address.

Suggested change
print("Listen on {f}\n", .{addr});
print("Listen on {any}\n", .{addr});

Copilot uses AI. Check for mistakes.


// we did not set the NONBLOCK flag (socket type flag),
// so the program will wait until data is received
Expand Down
18 changes: 8 additions & 10 deletions assets/src/05-01.zig
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,22 @@ pub fn main() !void {
const uri = try std.Uri.parse("http://httpbin.org/headers");
const buf = try allocator.alloc(u8, 1024 * 8);
defer allocator.free(buf);
var req = try client.open(.GET, uri, .{
.server_header_buffer = buf,
var req = try client.request(.GET, uri, .{
.extra_headers = &.{.{ .name = "Custom-header", .value = "Custom Value" }},
});
defer req.deinit();

try req.send();
try req.finish();
try req.wait();
try req.sendBodiless();

var iter = req.response.iterateHeaders();
var redirect_buffer: [1024]u8 = undefined;
var response = try req.receiveHead(&redirect_buffer);
var iter = response.head.iterateHeaders();
while (iter.next()) |header| {
std.debug.print("Name:{s}, Value:{s}\n", .{ header.name, header.value });
}

try std.testing.expectEqual(req.response.status, .ok);

var rdr = req.reader();
const body = try rdr.readAllAlloc(allocator, 1024 * 1024 * 4);
try std.testing.expectEqual(response.head.status, .ok);
const body = try response.reader(&.{}).allocRemaining(allocator, .unlimited);
defer allocator.free(body);

print("Body:\n{s}\n", .{body});
Expand Down
28 changes: 9 additions & 19 deletions assets/src/05-02.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,23 @@ pub fn main() !void {

const uri = try std.Uri.parse("https://httpbin.org/anything");

const payload =
\\ {
\\ "name": "zig-cookbook",
\\ "author": "John"
\\ }
;

var buf: [1024]u8 = undefined;
var req = try client.open(.POST, uri, .{ .server_header_buffer = &buf });
var req = try client.request(.POST, uri, .{
.extra_headers = &.{.{ .name = "Content-Type", .value = "application/json" }},
});
defer req.deinit();

req.transfer_encoding = .{ .content_length = payload.len };
try req.send();
var wtr = req.writer();
try wtr.writeAll(payload);
try req.finish();
try req.wait();
var payload: [7]u8 = "[1,2,3]".*;
try req.sendBodyComplete(&payload);
var buf: [1024]u8 = undefined;
var response = try req.receiveHead(&buf);

// Occasionally, httpbin might time out, so we disregard cases
// where the response status is not okay.
if (req.response.status != .ok) {
if (response.head.status != .ok) {
return;
}

var rdr = req.reader();
const body = try rdr.readAllAlloc(allocator, 1024 * 1024 * 4);
const body = try response.reader(&.{}).allocRemaining(allocator, .unlimited);
defer allocator.free(body);

print("Body:\n{s}\n", .{body});
}
51 changes: 27 additions & 24 deletions assets/src/05-03.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const std = @import("std");
const log = std.log;
const WebSocket = std.http.WebSocket;
const Request = std.http.Server.Request;
const Connection = std.net.Server.Connection;

Expand All @@ -11,7 +10,7 @@ pub fn main() !void {
var server = try std.net.Address.listen(addr, .{ .reuse_address = true });
defer server.deinit();

log.info("Start HTTP server at {any}", .{addr});
log.info("Start HTTP server at {f}", .{addr});
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format specifier {f} is for floating point numbers, but addr is a network address. This should be {any} to properly format the address.

Suggested change
log.info("Start HTTP server at {f}", .{addr});
log.info("Start HTTP server at {any}", .{addr});

Copilot uses AI. Check for mistakes.


while (true) {
const conn = server.accept() catch |err| {
Expand All @@ -29,31 +28,31 @@ pub fn main() !void {
fn accept(conn: Connection) !void {
defer conn.stream.close();

log.info("Got new client({any})!", .{conn.address});
log.info("Got new client({f})!", .{conn.address});
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The format specifier {f} is for floating point numbers, but conn.address is a network address. This should be {any} to properly format the address.

Suggested change
log.info("Got new client({f})!", .{conn.address});
log.info("Got new client({any})!", .{conn.address});

Copilot uses AI. Check for mistakes.


var read_buffer: [MAX_BUF]u8 = undefined;
var server = std.http.Server.init(conn, &read_buffer);
while (server.state == .ready) {
var recv_buffer: [1024]u8 = undefined;
var send_buffer: [100]u8 = undefined;
var connection_br = conn.stream.reader(&recv_buffer);
var connection_bw = conn.stream.writer(&send_buffer);
var server = std.http.Server.init(connection_br.interface(), &connection_bw.interface);
while (server.reader.state == .ready) {
var request = server.receiveHead() catch |err| switch (err) {
error.HttpConnectionClosing => return,
else => return err,
};

var ws: WebSocket = undefined;
var send_buf: [MAX_BUF]u8 = undefined;
var recv_buf: [MAX_BUF]u8 align(4) = undefined;

if (try ws.init(&request, &send_buf, &recv_buf)) {
// Upgrade to web socket successfully.
serveWebSocket(&ws) catch |err| switch (err) {
error.ConnectionClose => {
log.info("Client({any}) closed!", .{conn.address});
break;
},
else => return err,
};
} else {
try serveHTTP(&request);
switch (request.upgradeRequested()) {
.other => |other_protocol| {
log.err("Not supported protocol, {s}", .{other_protocol});
return;
},
.websocket => |key| {
var ws = try request.respondWebSocket(.{ .key = key orelse "" });
try serveWebSocket(&ws);
},
.none => {
try serveHTTP(&request);
},
}
}
}
Expand All @@ -63,16 +62,20 @@ fn serveHTTP(request: *Request) !void {
"Hello World from Zig HTTP server",
.{
.extra_headers = &.{
.{ .name = "custom header", .value = "custom value" },
.{ .name = "custom-header", .value = "custom value" },
},
},
);
}

fn serveWebSocket(ws: *WebSocket) !void {
try ws.writeMessage("Message from zig", .text);
fn serveWebSocket(ws: *std.http.Server.WebSocket) !void {
try ws.writeMessage("Hello from Zig WebSocket server", .text);
while (true) {
const msg = try ws.readSmallMessage();
if (msg.opcode == .connection_close) {
log.info("Client closed the WebSocket", .{});
return;
}
try ws.writeMessage(msg.data, msg.opcode);
}
}
15 changes: 10 additions & 5 deletions assets/src/10-01.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,15 @@ pub fn main() !void {

// Serialize JSON
value.verified = false;
const new_json_str = try json.stringifyAlloc(allocator, value, .{ .whitespace = .indent_2 });
defer allocator.free(new_json_str);
var out = std.Io.Writer.Allocating.init(allocator);
defer out.deinit();
var stringifier = json.Stringify{
.writer = &out.writer,
.options = .{
.whitespace = .indent_2,
},
};
try stringifier.write(value);

try testing.expectEqualStrings(
\\{
Expand All @@ -43,7 +50,5 @@ pub fn main() !void {
\\ "admin"
\\ ]
\\}
,
new_json_str,
);
, out.writer.buffered());
}
Loading
Loading