el-zen-de-zig

The Zen of Zig: Explicit Code, No Surprises

  • 4 min

The Zen of Zig is a philosophy of explicit and predictable design. Every language has a personality: Python values readability, C++ values backward compatibility, and Zig values that what happens is visible in the code.

Zig has a very clear, pragmatic, and direct philosophy. It focuses on maintainability.

Andrew Kelley, the creator of Zig, starts from a simple premise: we spend much more time reading code than writing it. Therefore, the language should be optimized to facilitate reading and comprehension, even if that means being a bit more verbose to write.

If you have Zig installed, you can see these principles by typing in your terminal:

zig zen
Copied!

Let’s focus on two principles from that list: no hidden control flow and no hidden memory allocations.

No Hidden Control Flow

In many modern languages, reading a line of code doesn’t guarantee you know what will happen. Let’s look at a theoretical example in a language like C++ or C#:

// Example in C++ / C#
var a = b + c;
Copied!

What does this line do?

  1. Is it a simple mathematical addition?
  2. Or is b an object that has an overloaded + operator, which executes a complex function that takes 5 seconds and connects to a database?
  3. Could that operation throw an exception and make the program jump to a distant catch block, interrupting the normal flow?
  4. Could a be a property (setter) that executes logic when a value is assigned to it?

You don’t know. To know, you have to know the definition of the types, read the documentation, and understand the entire context.

In Zig, the rule is strict: If you don’t see it, it doesn’t happen.

  • No operator overloading: + is always addition. If you want to add two vectors, you call vectorAdd(a, b). It’s longer to write, yes, but when reading it, you know exactly that it’s a function call.
  • No exceptions: Error handling is explicit through return values. The flow never “jumps” due to an invisible exception.
  • No properties: Accessing structure.field is just reading memory. If you want logic, you use structure.getField().

This feature makes the code easier to review. You don’t have to keep a complex map of invisible side effects in your head.

No Hidden Memory Allocations

This principle is especially important in systems, game engines, and embedded devices.

In high-level languages (Java, JS, Python), the Garbage Collector allocates and frees memory for you. It’s convenient, but you lose control over when it happens (the dreaded GC pauses).

Even in C++ or Rust, the standard library often allocates memory “under the hood.” If you use std::vector in C++ or Vec in Rust and add elements, the language will ask the operating system for memory (heap) automatically.

In Zig, the standard library cannot allocate memory unless you explicitly allow it.

If a function needs dynamic memory, it must accept an allocator parameter.

// In Zig, the intent is explicit
// We clearly see that this function needs memory to work
fn concatenate(allocator: Allocator, a: []u8, b: []u8) ![]u8 {
    ...
}
Copied!

This has profound implications:

  1. You know which operations can allocate memory: by looking for the allocator parameters, you can locate the points where dynamic memory is requested.
  2. You choose the strategy: you can provide a general-purpose allocator, one based on a fixed buffer, or an arena to free several allocations at once. The function only uses the received allocator.

For embedded systems (like Arduino or Raspberry Pi Pico), this is crucial. You can write code that guarantees zero dynamic allocations, avoiding memory fragmentation and unexpected hangs.

Communicate Intent Precisely

The Zen of Zig tells us: “Communicate intent precisely”.

Sometimes, Zig can seem a bit “pedantic.” It forces you to handle every possible error. It forces you to decide which integer type to use (u8, i32, usize) instead of using a generic int. It forces you to pass the allocator.

That extra effort upfront usually pays off later. The result is software that is:

  • Robust: You’ve had to think about edge cases.
  • Fast: No CPU cycles are wasted on hidden work.
  • Portable: By not relying on a heavy runtime environment, your code runs the same on a supercomputer as on a microcontroller.