Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Naming conventions

Pepper code, including the standard library, follows one set of naming rules. The compiler does not enforce them, but every example in this book, the runtime, and the tools in this repository follow them, and code you contribute should too.

WhatStyleExamples
Structs, traits, type aliasesPascalCaseString, HashMap, IoResult
Generic parametersone capital letter, or PascalCaseT, K, V, TValue, TError
Data members (fields)snake_caseunit_name, is_file
Methods and free functionssnake_caseappend, remove_at, read_to_string
Parameters and local variablessnake_casecapacity, best_so_far
Constants (a const holding a fixed value)SCREAMING_CASEMAX_REQUESTS, LINE_WIDTH
Other module-scope variablessnake_caserequest_count
Private data members and methods_ then snake_case_readings, _is_empty
# Types are PascalCase; acronyms are written as words (`HttpClient`, not `HTTPClient`).
struct TemperatureLog {
    # Public data members are snake_case.
    var unit_name: String = "celsius";
    # Private data members start with `_`.
    var _readings: Array<int> = Array<int>();
    var _total: int = 0;

    # Methods and their parameters are snake_case.
    add_reading(degrees: int) {
        _readings.append(degrees);
        _total = _total + degrees;
    }

    average(): int {
        return _is_empty() ? 0 : _total / (_readings.count as int);
    }

    # A private method starts with `_` too.
    _is_empty(): bool {
        return _readings.count == 0;
    }
}

# Generic parameters are a single capital letter or PascalCase.
struct Pair<TFirst, TSecond> {
    var first: TFirst;
    var second: TSecond;

    construct(TFirst first, TSecond second) {
        this.first = first;
        this.second = second;
    }
}

# Free functions and local variables are snake_case.
largest_of<T>(values: Array<T>): T {
    var best_so_far = values[0];
    for (var value in values) {
        if (value > best_so_far) {
            best_so_far = value;
        }
    }
    return best_so_far;
}

main(): int {
    var log = TemperatureLog();
    log.add_reading(20);
    log.add_reading(24);
    assert(log.average() == 22, "average of the readings");
    assert(log.unit_name == "celsius", "public member");

    var pair = Pair<String, int>("answer", 42);
    assert(pair.second == 42, "generic struct");

    var numbers = Array<int>();
    numbers.append(3);
    numbers.append(9);
    numbers.append(4);
    assert(largest_of<int>(numbers) == 9, "generic function");
    return 0;
}

Types

A type name is PascalCase: each word starts with a capital letter, and there are no underscores. Write an acronym as a word, so IoError and HttpClient, not IOError or HTTPClient.

An enum’s cases are PascalCase too (Color.Red, Status.NotFound).

A generic parameter is either a single capital letter (T, U, K, V) or a PascalCase name, which by convention starts with T (TValue, TError). Use a single letter when the parameter’s role is obvious, and a name when a declaration has several parameters that a reader has to tell apart.

Members, functions, and variables

Everything else a program names is snake_case: lowercase words joined by underscores. That covers a struct’s data members, its methods, free functions, parameters, local variables, and module-scope variables.

The exception is a constant: a const, at module scope or in a function, whose value is fixed, such as a number, string, or character. Its name is SCREAMING_CASE, uppercase words joined by underscores (MAX_REQUESTS, LINE_WIDTH). A module-scope const that holds an object, such as const request_count = async.Atomic<int>(0);, is a variable and stays snake_case.

Spell words out. A name is read far more often than it is typed, so prefer iterate, index, and directory to iter, idx, and dir. Keep an abbreviation only when it is the usual name for the thing, such as io or c_str.

An extern declaration keeps the name the C library gives the function, whatever its style (GetCurrentProcessId), because that name is what the linker looks up. The same goes for the fields of a struct that mirrors a C struct.

Built-in type names

Unlike the conventions above, this rule is enforced. No top-level declaration may take the name of a built-in type: void, bool, char, byte, short, ushort, int, uint, long, ulong, float, double, and their sized spellings (i8, u8, i16, u16, i32, u32, i64, u64, f32, f64). Names resolve by spelling across the whole program, so such a declaration would take the type’s place in every file, the runtime’s included. The compiler reports it at the name:

main.pep:1:7: error: 'double' is the name of a built-in type; choose another name
    1 | const double = (x: int): int => x * 2;
      |       ^~~~~~

Locals, parameters and members may still use these names, since they are visible only where they are declared.

Private members

Pepper’s private keyword applies only to top-level declarations (see Visibility); any code that can reach an object can name all of its members. A leading _ marks a data member or method as private to its struct instead: code outside the struct, and its extensions, should not use it. The rest of the name is snake_case, as for a public member (_readings, _is_empty).

The standard library relies on this convention: its generated reference leaves _ members out, and doc comments are required only on members without the prefix.

A top-level declaration does not need the prefix to be private: it is internal to its module (or, outside any module, private to its file) unless it is exported, and private keeps it to its file (see Visibility). The runtime also uses the prefix on top-level helpers it exports but that are not part of its public API, such as _pep_malloc, which the compiler calls by name, or a helper one runtime module shares with another: the reference leaves them out too.