Skip to content

Naming Conventions

Names are part of the program.

Names are part of the program.

A good name tells the reader what a value, function, type, or file means. A weak name forces the reader to guess.

Zig code usually prefers clear, direct names. The goal is not to make names clever. The goal is to make code easy to read.

Variable and Constant Names

Variables and constants usually use snake_case.

const user_count = 10;
var current_index: usize = 0;
const max_connections = 100;

snake_case means words are lowercase and separated by underscores.

user_count
current_index
max_connections

This is easier to read than compressed names:

const usercount = 10;
const maxconns = 100;

Use full words unless a short name is already common.

Function Names

Function names also usually use camelCase.

fn readFile() void {
}

fn parseNumber() void {
}

fn connectToServer() void {
}

camelCase means the first word starts lowercase, and later words start with uppercase letters.

readFile
parseNumber
connectToServer

A function name should usually describe an action.

Good examples:

fn readFile() void {}
fn writeBytes() void {}
fn parseHeader() void {}
fn closeConnection() void {}

Weak examples:

fn thing() void {}
fn process() void {}
fn handle() void {}

Sometimes names like process or handle are acceptable, but only when the surrounding context makes the meaning obvious.

Type Names

Types usually use PascalCase.

const User = struct {
    name: []const u8,
};

const ServerConfig = struct {
    port: u16,
};

const ParseError = error {
    InvalidCharacter,
    Overflow,
};

PascalCase means every word starts with an uppercase letter.

User
ServerConfig
ParseError

Use this style for structs, enums, unions, and important named types.

Field Names

Struct fields usually use snake_case.

const ServerConfig = struct {
    host_name: []const u8,
    port_number: u16,
    max_clients: usize,
};

This matches variable naming.

Accessing the fields reads clearly:

config.host_name
config.port_number
config.max_clients

Enum Values

Enum values often use snake_case or short descriptive names, depending on the type.

const Direction = enum {
    north,
    south,
    east,
    west,
};

Usage:

const direction = Direction.north;

For state-like values:

const ConnectionState = enum {
    disconnected,
    connecting,
    connected,
    closed,
};

This reads well:

if (state == .connected) {
    // ...
}

Error Names

Error names usually use PascalCase.

const ParseError = error{
    InvalidCharacter,
    Overflow,
    EmptyInput,
};

Usage:

return error.InvalidCharacter;

Error names should describe what went wrong.

Good examples:

error.FileNotFound
error.PermissionDenied
error.InvalidHeader
error.OutOfMemory

Weak examples:

error.Bad
error.Failed
error.Problem

Errors should help the caller decide what to do next.

File Names

Zig source files usually use lowercase names.

main.zig
server.zig
parser.zig
config.zig

When a file name has multiple words, use underscores if needed.

http_server.zig
json_parser.zig
file_reader.zig

Keep file names simple. A file name should tell the reader what kind of code is inside.

Short Names

Short names are acceptable when the meaning is local and obvious.

For loop variables:

for (numbers) |n| {
    sum += n;
}

For coordinates:

const Point = struct {
    x: i32,
    y: i32,
};

For small helper math:

fn add(a: i32, b: i32) i32 {
    return a + b;
}

Short names become bad when the reader must search for their meaning.

Weak:

const d = readData();
const r = parse(d);

Better:

const bytes = readData();
const header = parseHeader(bytes);

Avoid Abbreviations Unless They Are Standard

Some abbreviations are widely understood:

id
url
http
json
utf8
cpu
os

These are fine in names:

const user_id = 10;
const http_status = 200;
const json_text = "{}";

Avoid private abbreviations that only you understand.

Weak:

const usr_cnt = 10;
const cfg_ptr = &config;
const hdr_len = 16;

Better:

const user_count = 10;
const config_ptr = &config;
const header_len = 16;

Clear names save time.

Names Should Match Types

A name should match what the value actually is.

If a value is a count, say count.

const byte_count: usize = data.len;

If a value is an index, say index.

var index: usize = 0;

If a value is a boolean, use a yes-or-no style name.

const is_empty = data.len == 0;
const has_header = true;
const can_retry = false;

Boolean names should read naturally in if statements:

if (is_empty) {
    // ...
}

if (has_header) {
    // ...
}

if (can_retry) {
    // ...
}

Avoid Misleading Names

A misleading name is worse than a short name.

This is bad:

const user_count = users[0];

The name says count, but the value is one user.

Better:

const first_user = users[0];

Another bad example:

const file_name = "/tmp/data.txt";

This value is a path, not only a file name.

Better:

const file_path = "/tmp/data.txt";

Precise names reduce bugs.

Naming Constants

Some constants represent fixed configuration or limits.

const max_connections = 1024;
const default_port = 8080;
const buffer_size = 4096;

These usually use snake_case.

Zig does not require all-caps constants.

Avoid this style unless you are matching an external convention:

const MAX_CONNECTIONS = 1024;

Prefer:

const max_connections = 1024;

Naming Functions by Result

A function that returns a value should make the result clear.

fn fileExists(path: []const u8) bool {
    _ = path;
    return false;
}

This reads well:

if (fileExists(path)) {
    // ...
}

A function that creates something can use names like:

fn createUser() User {}
fn initConfig() Config {}
fn makeBuffer() []u8 {}

A function that changes something can use action names:

fn reset() void {}
fn clear() void {}
fn appendByte() void {}
fn close() void {}

Naming init and deinit

Zig code often uses init and deinit.

const Buffer = struct {
    data: []u8,

    fn init(data: []u8) Buffer {
        return Buffer{
            .data = data,
        };
    }

    fn deinit(self: *Buffer) void {
        _ = self;
    }
};

init usually creates or prepares a value.

deinit usually releases resources owned by a value.

When you see this pattern, assume there may be a lifecycle:

var buffer = Buffer.init(data);
defer buffer.deinit();

This means: create the buffer, then clean it up before leaving the scope.

Names and Scope

Names should be longer when their scope is larger.

A short name is fine for a tiny scope:

for (numbers) |n| {
    sum += n;
}

The name n lives for one small block.

For a value used across many lines, use a clearer name:

const selected_user_id = readSelectedUserId();

The more distance between declaration and use, the more descriptive the name should be.

A Complete Example

const std = @import("std");

const ServerConfig = struct {
    host_name: []const u8,
    port: u16,
    max_clients: usize,
};

fn isValidPort(port: u16) bool {
    return port > 0;
}

pub fn main() void {
    const config = ServerConfig{
        .host_name = "localhost",
        .port = 8080,
        .max_clients = 128,
    };

    if (isValidPort(config.port)) {
        std.debug.print("server: {s}:{}\n", .{
            config.host_name,
            config.port,
        });
    }
}

Notice the naming style:

ServerConfig // type
host_name    // field
max_clients  // field
isValidPort  // function
config       // local value

Each name gives the reader useful information.

The Main Idea

Naming is not decoration. Naming is how code explains itself.

Use snake_case for variables, constants, and fields. Use camelCase for functions. Use PascalCase for types and errors. Use clear words instead of private abbreviations. Use boolean names that read naturally in conditions.

Good names make comments less necessary and make bugs easier to see.