# Where to Go From Here

### Where to Go From Here

The programs in this chapter are small, but they have the shape of larger Zig programs.

A command-line tool reads arguments, validates them, and calls ordinary functions.

A file program opens resources, copies bytes, and closes everything with `defer`.

A filter reads input in pieces and writes selected output.

A network client treats the socket as a byte stream.

A library exposes public declarations and keeps tests near the code.

These examples use the same habits:

```text
declare data clearly
check errors where they occur
release resources in the same scope that acquired them
keep I/O at the edge
move decisions into small functions
```

Zig rewards plain structure. There are no hidden constructors, destructors, exceptions, or global allocator rules. The program says what it uses.

A good next program is one that touches several parts of the language at once. For example:

```text
loggrep pattern file
```

It should:

```text
read command-line arguments
open a file
read it line by line
match text
print matching lines
return useful errors
include tests for matching logic
```

The matching logic can be separated from I/O:

```zig
const std = @import("std");

fn matches(line: []const u8, pattern: []const u8) bool {
    return std.mem.indexOf(u8, line, pattern) != null;
}

test "matches finds substring" {
    try std.testing.expect(matches("hello zig", "zig"));
}

test "matches rejects missing substring" {
    try std.testing.expect(!matches("hello zig", "rust"));
}
```

The I/O code can stay in `main`:

```zig
const std = @import("std");

fn matches(line: []const u8, pattern: []const u8) bool {
    return std.mem.indexOf(u8, line, pattern) != null;
}

pub fn main() !void {
    var args = std.process.args();

    _ = args.next();

    const pattern = args.next() orelse return error.MissingPattern;
    const path = args.next() orelse return error.MissingPath;

    const cwd = std.fs.cwd();

    var file = try cwd.openFile(path, .{});
    defer file.close();

    var reader = file.reader();
    var out = std.io.getStdOut().writer();

    var buffer: [4096]u8 = undefined;

    while (try reader.readUntilDelimiterOrEof(&buffer, '\n')) |line| {
        if (matches(line, pattern)) {
            try out.print("{s}\n", .{line});
        }
    }
}
```

This program is not large, but it is complete. It has arguments, files, buffers, slices, errors, tests, and output.

From here, make programs larger by adding one feature at a time.

Add line numbers:

```text
loggrep -n pattern file
```

Add inverted matching:

```text
loggrep -v pattern file
```

Add standard input:

```text
cat file | loggrep pattern
```

Add case-insensitive matching.

Add tests before changing the matching function.

This is the safest way to learn Zig. Each new feature forces one new rule of the language into use.

The rest of this book should now be read in both directions. Earlier chapters explain the parts. This chapter shows how the parts form a program.

Exercise 20-41. Build `loggrep`.

Exercise 20-42. Add `-n` for line numbers.

Exercise 20-43. Add `-v` for inverted matching.

Exercise 20-44. Add tests for each flag.

Exercise 20-45. Package the program with `build.zig` and `build.zig.zon`.

