ZigBasics

Zig Variables: Types and Mutability

TT
TopicTrick Team
Zig Variables: Types and Mutability

Zig Variables: Types and Mutability

In languages like JavaScript or Python, a "Number" is an abstract concept hidden behind a 64-bit float. In C, a "Number" is an ambiguous int that might be 16, 32, or 64 bits depending on which CPU you bought. In Zig, a "Number" is defined by its Exact Bit-Size and its Memory Sign.

Zig provides explicit control over every byte of your application's state. This 1,500+ word guide explores the "Immutable" philosophy of Zig, the revolutionary Optional Types that eliminate the "Null Pointer Exception" from your career, and the unique performance benefits of Undefined Memory.


1. Const vs. Var: The "Safety" Default

In Zig, there are two ways to define state:

  • const: Immutable. Once set, it can never change. It is essentially a named constant.
  • var: Mutable. It can be reassigned during runtime.

The Architect's Rule: Const-by-Default

In professional Systems Architecture, always start with const. Only change it to var if the application logic explicitly requires mutation. Why?

  1. Readability: If a variable is const, the reader knows its value is stable for the rest of the function.
  2. Safety: Prevents "Spooky Action at a Distance" where one part of your code accidentally changes data that another part is using.
  3. Optimization: The compiler can often place const values directly into the binary's read-only memory, reducing RAM usage.
zig

2. The Physics of the Stack: Mapping Data to Silicon

To a developer, a variable is just a name. To the hardware, a variable is a Memory Address or a CPU Register.

The Register Mirror

  • The Process: For small variables (like a u32 or u64), the Zig compiler often skips RAM entirely. It places the value directly in a General Purpose Register (like RAX or RDX on x86).
  • The Physics: Accessing a register takes ~1 clock cycle. Accessing RAM can take ~200 clock cycles.
  • The Optimization: By using const, you signal to the compiler that the value will never change, allowing it to keep that data in a register for the entire life of your function, resulting in lightning-fast execution.

3. Integer Precision: Breaking the int Habit

Zig makes integer sizes a "First-Class Citizen." You are forced to choose the size that fits your data:

  • unsigned (u8, u16, u32, u64, u128): Non-negative numbers. Use u8 for bytes (0-255), u32 for IDs, and u64 for timestamp math.
  • signed (i8, i16, i32, i64, i128): Supports negative numbers.
  • isize and usize: These are "Platform Dependent." They match the size of a pointer on the target machine (e.g., 64-bit on a modern PC, 32-bit on an old Raspberry Pi).

Comptime Literals: comptime_int

In Zig, a number literal like 10 doesn't have a type yet. It is a comptime_int. This means it has Infinite Precision. You can perform massive calculations on literals at compile-time without losing a single bit, and only "Coerce" them to a smaller type (like u8) at the very end.

zig

3. Optional Types: The Null Slayer

In C or C++, a pointer can be NULL. If you forget to check it and try to read it, your program crashes with a "Segfault." In C#, this is the "Null Reference Exception."

Zig solves this by making every type Non-Nullable by default. A u32 can never be null. It must always be a number. If you need a "Maybe" value, you must use the Optional Type (?).

The Anatomy of the ?

zig

The Unwrapping Pattern: if and orelse

You cannot perform operations on an Optional. You must Unwrap it first.

zig

Memory Deep-Dive: For types like Pointers, Zig's compiler is smart enough to use the "Zero Value" of the pointer to represent null. This means an Optional Pointer takes Zero extra bytes of memory compared to a regular pointer. Total safety with zero performance cost.


5. Integer Overflow Physics: Safe vs. Wraparound

In C, adding 1 to the maximum value of a signed integer is Undefined Behavior (UB). The computer might crash, or it might silently wrap around to a negative number.

The Zig Safety Lever

Zig provides explicit operators for how your hardware handles overflow:

  • + (Standard): In "Debug" or "ReleaseSafe" modes, this will trigger a Runtime Panic if it overflows. In "ReleaseFast," it is undefined.
  • +% (Wraparound): Tells the CPU to perform "Modulo Arithmetic." If you hit the max, it wraps to zero. This is a common pattern in hashing and encryption logic.
  • +| (Saturated): If you hit the max, it stays at the max. Excellent for "Health Bars" in games or "Volume Levels" in audio software.

The Mirror: By choosing the right operator, you are directly controlling the ALU (Arithmetic Logic Unit) of your processor.


6. Type Inference: Clean but Strict

Zig is statically typed, but it doesn't require "Boilerplate."

zig

The Catch: Zig does NOT allow "Implicit Casting." You cannot add a u8 to a u16 directly. You must explicitly convert it using @as() or @intCast(). This prevents the "Silent Overflow" bugs that plague C and C++ projects.


5. undefined: The Performance Secret

If you create a variable but aren't ready to give it a value yet, you can use undefined.

zig

When you use undefined, the compiler does not write zeros to that memory. It just "Leaves it as it is."

  • The Pro: Extreme speed for large memory allocations.
  • The Con: If you read undefined memory in a "Debug" build, Zig will often fill it with 0xAA values so you can detect the mistake immediately. In "Release" builds, it is pure garbage. Mastery Pattern: Use undefined for large data buffers that will be filled by a function (like file.read()) immediately afterward.

6. Type Coercion: Widening vs. Narrowing

Zig allows "Safe" coercion.

  • Widening (Safe): You can always put a u8 into a u16 because it "Fits." Zig does this automatically.
  • Narrowing (Dangerous): Putting a u64 into a u8 might lose data. Zig forbids this automatically. You must use @intCast(value) to tell the compiler: "I have checked the math, and I know it fits."

Summary: The Data Checklist

  1. Immutability: Default to const. Use var only for loop counters or accumulators.
  2. Explicit Bit-Width: Don't guess. Use i32 if you need signs, u32 if you don't. Use usize for arrays.
  3. The Optional Guard: Use ? for every field that isn't 100% guarenteed to exist.
  4. No Implicit Casts: If the compiler complains about types, use @intCast or @floatCast explicitly.
  5. Initialization: Use 0 by default, and undefined only for high-performance buffers.

Variables are the "Atoms" of your software. By mastering the precision of bit-widths and the safety of optional unwrapping, you gain the ability to build systems that are both faster and safer than anything written in legacy languages. You graduate from "Managing data" to "Architecting Memory."


Phase 3: Memory Mastery Checklist

  • Audit your project's variables: Can 90% of your var declarations be converted to const?
  • Implement Saturated Arithmetic (+|) for a non-breaking logic path.
  • Use Optionals (?) to replace every pointer that could potentially be null.
  • Test the Zero-Cost Pointer Optimization: Verify that ?*u8 and *u8 occupy the same 8 bytes in memory.
  • Leverage undefined for large buffers that are populated immediately after allocation.

Read next: Pointers, Slices, and Arrays: Navigating the Memory Grid →


Part of the Zig Mastery Course — engineering the atoms.