Code style for C and C++

One set of naming and lifecycle rules for both languages. C++ adds explicit ownership and a small set of own types in public interfaces.

Status
Draft
Version
0.1
Last updated

Rules for both languages

  • snake_case for functions and variables, Pascal_case for types, ALL_CAPS for constants. File-private state is static (C) or private (C++); the keyword says it, the name does not need to, so a private field has no leading underscore. Reason: the C standard library is spelled in lowercase, so this is the closest fit to the canonical form.
  • In a definition, a space before the parenthesis; in a call, none. Reason: definitions become easy to find by text search (a local convention, see the overview for its limits).
  • Every resource has the lifecycle init, use, free, with the pair visible to the caller. No hidden allocation.
  • Expected failures are return codes or a small status type. Abort paths are for programmer errors, not for validation.
  • Document which pointers may be null. Use assert for invariants that hold after a successful init.
  • One primary concept per module; no misc.c or misc.cc.
  • Named constants instead of magic numbers.
  • Braces: Allman, with a narrow exception for a single-line guard such as an early return.

Additional rules for C++

  • Headers are .h, implementation files are .cc.
  • Public interfaces show the project's own types, not standard library types. Reason: a library type in a header becomes part of the contract.
  • Pointers carry meaning by convention: Type* may be null and the caller checks, Type& must exist, a slice is borrowed for the caller's lifetime.
  • Expected failures return a Result. Exceptions are not used for ordinary validation.
  • Heap allocation lives inside a dedicated data-structure module. Everything else defaults to the stack.
  • Rules that exist only for C++ (templates, slices) are not applied to plain C.

Generator mapping

Both languages keep the canonical word order and separators. A project prefix keeps the flat C namespace tidy.

CanonicalCC++
load_url (function)load_urlload_url
web_view_configuration (type)Web_view_configurationWeb_view_configuration
minimal_ui (constant)MINIMAL_UIMINIMAL_UI

A binding that targets C++ users of an existing library may choose that library's spelling instead; the canonical name stays the same.

Example in C

typedef struct Reader Reader;

Reader* reader_open (const char* path);
int reader_read (Reader* reader, char* buffer, size_t capacity);
void reader_close (Reader* reader);

int copy_first_block (const char* path, char* buffer, size_t capacity)
{
    Reader* reader = reader_open(path);
    if (reader == NULL) return -1;

    int count = reader_read(reader, buffer, capacity);
    reader_close(reader);
    return count;
}

Example in C++

Result open_page (Page_view& view, const Url& url)
{
    if (!url.is_valid())
    {
        return Result::error(ERR_INVALID_URL);
    }
    return view.load_url(url);
}

Counter-arguments

  • Widely used C styles, such as the Linux kernel's, use tabs and K&R braces. Own-line braces are uncommon in C.
  • Many C libraries prefix names instead of using a Pascal_case type name, which keeps everything lowercase.
  • A text search for name ( finds definitions only if every declaration, function pointer and macro follows the same rule. They often do not.
  • The C++ Core Guidelines recommend managing resources automatically with RAII and using unique_ptr or shared_ptr to represent ownership. This convention deliberately goes the other way for C++: explicit, visible acquire and release calls, and no standard smart pointers in this ownership model. The price is more manual code and the loss of what many C++ programmers and tools expect.
  • Keeping standard library types out of public headers costs conversions and duplicates well-tested code.

Sources