[back]

Coding Style

Preface

This is my personal style guide for Jai. This site is more a reference for myself and for people who work on my code.

Inspired by suckless.org.

Index

Show
  1. File Layout
  2. Comments
  3. Line Length
  4. Block Formatting
  5. Macros
  6. Tabs vs Spaces & Width
  7. Naming
  8. Namespaces
  9. Switch

File Layout

Jai allows the usage of #scope_file, #scope_module, and #scope_export. The ugly pattern is, that some people switch between file-scope and module-scope, which looks (to me) chaotic and forces your brain to do extra work.

This is why in my code #scope_file appears only once per file and #scope_module only exists, if I have to use #scope_export.

To keep the separation even cleaner, a general structure (GS) is maintained per scope, not per file.

This is the general structure:

And this is how a file is structured:

Here are some examples:

Ex. 1, commonly used

server_offline: bool;
thread_group: Thread_Group;


DIR_WWW       :: "./www";
DIR_TEMPLATES :: "./templates";


Export_Kind :: enum {
    NONE;
    HTML;
    PDF;
    TXT;
}


Work_Data :: struct {
    item: *Item;
    page: *Page;
}


main :: () {
    /** snip */
}

path_remove_match :: (match: string, path: string) -> string {
    /** snip */
}


#scope_file


work_data: Work_Data;
last_error: int;


MAX_CPUS :: 200;
THREAD_LOGGING_ENABLED :: false;


Item :: struct {
    /** snip */
}

Page :: struct {
    /** snip */
}


dir_swap_atomic :: (dir_temp: string, dir_live: string) -> ok: bool {
    /** snip */
}

has_hyper_threading :: () -> bool {
    /** snip */
}


#import "Basic";
#import "Thread";
#import "System";
#import "Machine_X64";

#import "stringpad";
#import "htmltemplate";

#load "../shared.jai";
#load "../config.jai";
#load "magic.jai";
    
Ex. 2, export procedures

/** Some explaination / documentation about the public APIs */


#scope_export


important_handle: s32;
another_handle: s32;


api_something :: (s: *u8, len: u64) -> bool {
    /** snip */
}

api_something2 :: (handle: s32, proc: *void) -> s32 {
    /** snip */
}


#scope_module


foo: string;
bazz: int;


Bar :: enum {
    YAY;
    NAY;
}


#scope_file


dir_swap_atomic :: (dir_temp: string, dir_live: string) -> ok: bool {
    /** snip */
}

has_hyper_threading :: () -> bool {
    /** snip */
}


#import "Basic";
    

Comments

If a comment is some sort of documentation use /** Important things */.
If it's a short note or todo use // Note(Bob): Don't forget this stuff here.

If you need to visually separate sections, use:


/* ------------- *
 * Path Routines *
 * ------------- */
    

The usage of comments should be sparse. Only two types of comments are allowed:

Avoid documenting obvious things like this:


/** This procedure creates an axis-aligned bounding box, based on a rectangle.

@param rect: Takes an `Rectangle` type
@return: An axis-aligned bounding box

@errors: none
*/
make_aabb :: (rect: Rectangle) -> Aabb {
    a: Aabb;
    a.upper_bound = { rect.x, rect.y };
    a.lower_bound = { rect.x + rect.width , rect.y + rect.height };
    return a;
}
    
The name, input, and return type already indicates what is happening.

This is better:


/** DFS. Favors the left side. Callback can decide how to proceed with the tree-walking */
tree_visit :: (
    tree: *Tree,
    user_data: $T,
    proc: (info: Tree_Visitor_Info, user_data: T) -> Tree_Visit_State,
    start_index := -1
)
    -> ok: bool
{
    /** snip */
}
    

Line Length

Keep it to max 94 characters (arbitrary chosen, no deeper reasoning).

Block Formatting

Some procedures will hit the line limit, so this is how I break it up:


/** Pretty long proc */
foo_bar_looong_name :: (
    thing1: string,
    thing2: int,
    thing3: (int) -> (bool),
    thing4: bool
)
    -> (ok: bool, idx: int)
{
    /** snip */
}

/** When there are only two or three simple params, I let them in one line */
foo_bar_thing_but_also_a_long_name :: (
    some_param: string, another_param: int,
) {
    /** snip */
}
    

Macros

If possible, macros should be used in the same scope, where they're needed.
For example, if you have a lexer procedure, put the traversal macros inside it.

The reason for that is locality of behaviour.

It allows you to understand the code quicker, because you don't have to traverse so much around the codebase and switch mental context.


format_date :: (time: Apollo_Time, fmt: string) -> string {

    is_at_end :: () -> bool #expand {}

    pop :: () -> u8 #expand {}

    peek :: () -> u8 #expand {}

    parse_fmt :: (idx: int, cal: Calendar_Time, fmt: string) -> string {}


    while !is_at_end() {
        /** snip */
    }
}
    

Tabs vs Spaces & Width

I personally prefer spaces. But this is because I got Python-brain-damaged. There's no deeper reason for it.

And regarding the width: at least four characters. Anything below is just exhausting to read after hours of coding.

Naming

How to name things is adapted from Jai:

Jai allows to break up variable names like this:


my_\      _variable_name: string;
my_amazing_variable_name: string;
    

Which is a neat feature, but then you can delete grep. So please avoid it.

Namespaces

Currently I don't really use namespaces, I prefix procedures with a specific name. Like tree_add() and tree_remove(). And shared or helper procedures are named without a prefix and below #scope_file.


tree_add :: () {
    /** snip */
    traverse();
}

tree_remove :: () {
    /** snip */
    traverse();
}


#scope_file


traverse :: () {}
    

I tried to use namespaces, but then this pattern, Foo.thing(), adds so much noise.
I'm aware that I could use using Foo;, but this will also add line-noise.


make_something_cool :: (
    group: *Th.Thread_Group,
    thread: *Th.Thread,
    work: *void
)
    -> Th.Thread_Continue_Status
{
    /** snip */
}
    

Just prefixing a name and go with pascal_case makes the code more uniform - at least for my eyes.

Switch

Use this indentation:


if foo == {
case .ONE;
    do_thing();

case .TWO;
    do_other_thing();

case;
    log("Something");
}
    

If the code is actually simple like the example above:


if foo == {
case .ONE; do_thing();
case .TWO; do_other_thing();
case;
    log("Something");
}
    
Published: 2026-07 / Updated: 2026-07