Style guide

There are better things to argue about than how your code looks, so Hare has a single canonical programming style which is considered correct. We, the designers of the Hare programming language, declare the coding style declared herein to be correct, and all others to be incorrect.

General conventions

These apply generally to constructs found throughout Hare programs.

  1. Hare source files MUST be indented with tabs. The tab size SHOULD be 8 columns.

  2. Lines SHOULD be limited to 80 columns in width, unless it would break up an error string, which would prevent grepping for errors.

  3. When breaking a long line into several, subsequent lines MUST be indented once – and MUST NOT be aligned vertically to align with features on the previous line. If the following line would be indented due to the introduction of a new block, the continuation line MUST be indented twice to visually distinguish it from the block.

  4. (subjective) When breaking a long line into several, items SHOULD be distributed to achieve “balance”, such that if a line were drawn down the middle of the expression, an approximately equal number of characters would fall to either side.

  5. The ; following the end of an expression MUST be placed on the final line of that expression with no space between ; and the last token of that expression.

  6. All lines MUST NOT end in a whitespace character (space or tab).

  7. If the name of a symbol is defined by a third-party (such as a C library, or a specification), programs SHOULD prefer to use the naming conventions of the third-party rather than rewriting names to use the Hare conventions.

Correct example
let result = frobnicate_the_frobs(scary_frob,
     sporty_frob, baby_frob, ginger_frob, posh_frob);

if (was_frobbed_correctly(frob_context, FROB_RESULT_SUCCESS,
             FROB_STANDARD_IEEE_7553, result)) {
     return true;
};
Incorrect example
let result = frobnicate_the_frobs(scary_frob,
                               sporty_frob,
                               baby_frob,
                               ginger_frob,
                               posh_frob);

if (was_frobbed_correctly(frob_context, FROB_RESULT_SUCCESS,
     FROB_STANDARD_IEEE_7553, result)) {
     return true;
};

Source file organization

A Hare module is made up of one or more files in a directory.

  1. Hare source files SHOULD be named in lower_underscore_case, with the .ha file extension. Their mimetype is text/x-hare.

  2. Hare source files may be named with only a tag (e.g. +linux.ha) if appropriate, but MUST NOT be named .ha.

  3. Each Hare source file MUST list its imports, followed by its declarations, with one empty line between them. This empty line MUST NOT be included if there are no imports.

  4. Use statements MUST be sorted alphabetically.

  5. Declarations which require a single line MAY follow one after the other; but declarations which require multiple lines MUST be separated by a single empty line.

Correct example
use bar;
use baz;
use foo;

let x: int = 10;
let y: int = 10;

type my_type = struct {
     x: int,
     y: int,
     z: int,
};

fn foobar() void = {
     // ...
};

export fn main() void = {
     // ...
};

Function declarations

These rules govern the declaration of Hare functions and function prototypes.

  1. The export status, fn keyword, name, parameter list, return type, and the = and { tokens, MUST be on the same line if they fit within 80 columns.

  2. If these tokens would not fit on the same line, the export status, fn keyword, name, and opening parenthesis of the parameter list MUST be placed on the first line; then each parameter placed on subsequent lines, indented once; and on their own line, the ), return type, =, and { tokens. In this case, the final parameter MUST end with an extra , token, unless the function is variadic.

  3. Prototypes MUST obey the same rules, but will omit the = and { tokens, and MUST place the semicolon on the final line following the return type.

  4. Function bodies MUST be indented once.

  5. Functions whose bodies are not an expression list { ... } MAY place their bodies on the next line, indented once.

Correct example
export fn main() void = {
     do_work(1, 2);
};

fn do_work(x: int, y: int) void = {
     // ...
};

fn many_parameters(
     param_one: int,
     param_two: int,
     param_three: int,
     param_four: int,
     param_five: int,
) void = {
     // ...
};

fn many_variadic(
     param_one: int,
     param_two: int,
     param_three: int,
     param_four: int,
     params: int...
) void = {
     // ...
};

Type declarations

Rules governing the declarations of types.

  1. Spaces MUST be placed between the type token, the type name, the = token, and the type specifier. All of these tokens MUST be on the same line.

  2. Type aliases MUST be named in lower_underscore_case.

Correct example
type my_type = int;

Constant declarations

Rules governing constant declarations.

  1. A space MUST NOT be placed between the constant name and the : token. A space MUST be placed between the : token and the type specifier.

  2. Spaces MUST be placed both before and after the = token.

  3. Constants MUST be named in UPPER_UNDERSCORE_CASE.

Correct example
def MY_CONSTANT: int = 1234;

Global declarations

Rules governing global variable declarations. Note that the use of globals is often undesirable, as may limit your ability to expand upon or compartmentalize an interface later.

  1. A space MUST NOT be placed between the constant name and the : token. A space MUST be placed between the : token and the type specifier.

  2. Spaces MUST be placed both before and after the = token.

Correct example
let my_global: int = 1234;

Type specifiers

Rules governing the format of types.

Struct types

  1. Structs MUST be defined with a space between the struct and { tokens.

  2. Structs MAY be defined in either single-line or multi-line style.

  3. In the multi-line style, a newline MUST follow the { token, followed by the struct fields indented once, followed by the } token without indentation. The final field MUST include the optional , token in this style.

  4. In the single-line style, the { token MUST be followed by a space, followed by the struct fields (each separated by a space following the , token, except for the final field, which MUST omit the , token), then a space and the } token.

  5. A field name MUST NOT be separated from the : token by a space, but a space MUST be placed between the : token and the field type.

  6. Struct fields MAY be grouped by purpose, with each group separated by a single empty line.

  7. Within each group of struct fields, fields MAY be alphabetized by name if the subsequent impact on the struct’s storage representation is not of consequence.

Correct example
struct {
     x: int,
     y: int,
     z: int,

     metadata: struct {
             foo: int,
             bar: int,
             baz: int,
     },
};

Union types

  1. Union types are considered equivalent to struct types for matters of style, with the union token used in place of the struct token.

Array & slice types

  1. There MUST NOT be a space between the [ token, length expression (if present), ] token, and member type.

  2. The use of arrays is preferred when possible, as the extra indirection of a slice type incurs a performance cost.

Correct examples
[]int
[5]int
[2 + 2]int
[*]int
[_]int
Incorrect examples
[ ]int
[ 10 ]int
[] int

Tagged union types

  1. Tagged union types may be specified in either a single-line or multi-line style. If the type would fit on a single line within 80 columns, the single-line style MUST be used.

  2. In the single-line style, there MUST NOT be a space between the ( or ) token and the member type list.

  3. In the single-line style, there MUST be a space between the | tokens and each member type.

  4. (subjective) In the multi-line style, the programmer MAY use their discretion to distribute the member types to achieve “balance”.

  5. When breaking to a new line, place the | on the first line. Place the final ) token on the same line as the final members.

  6. (subjective) When using a tagged union with many member types, consider categorizing them into additional aliases and using the ... operator to unwrap them.

  7. Tagged union aliases SHOULD be named in the singular.

Correct examples
type my_union = (type_a | type_b | type_d | type_e | type_f | type_g);

type my_result = (result_type_a | result_type_b | result_type_c |
     result_type_d | result_type_e);

type my_error = (error_type_a | error_type_b | error_type_d | error_type_c);

type my_union = (...my_result | ...my_error);

Tuple types

  1. Tuple types may be specified in either a single-line or multi-line style. If the type would fit on a single line within 80 columns, the single-line style MUST be used.

  2. A tuple type MUST NOT place a space after ( or before ).

  3. In the single-line style, a tuple type MUST NOT place a space before each ,, and MUST place a space after each ,, except for the last, which MUST be omitted.

  4. In the multi-line style, a tuple type MUST NOT place a space before each ,, and MUST place a space after each ,, except for the last, which MUST NOT be omitted.

  5. (subjective) In the multi-line style, the programmer MAY use their discretion to distribute the member types to achieve “balance”.

  6. When breaking to a new line, place the , on the first line. Place the final ) token on the same line as the final members.

Correct example
type my_tuple = (int, uint);
type my_tuple = (type_a, type_b, type_c,
     type_d, type_e, type_f);

Pointer types

  1. Pointer types MUST NOT have a space between the * token and the secondary type.

  2. Nullable pointer types MUST have a space between the nullable token and the * token.

  3. Function pointer types MUST NOT have a space between the fn token and the parameter list.

  4. Function pointer types MAY omit the parameter name from each parameter in the parameter list.

Enum types

  1. Enum aliases SHOULD be named in the singular.

  2. Members MUST be named in ALL_CAPS.

Correct example
type character_flag = enum {
     BIG,
     FRIENDLY,
     GIANT,
};

Values

Rules governing the style for representations of values.

  1. When choosing between explicit and hinted types, prefer whichever produces a shorter program.

Correct example
let x = 0z;

let y: [_]u8 = [1, 2, 3];

let z: [_]nullable *size = [&x, null];
Incorrect example
let x: size = 0;

let y = [1u8, 2u8, 3u8];

let z = [&x: nullable *size, null: nullable *size];

Struct values

  1. Struct values MUST be defined with a space between the struct and { tokens, or between the type alias name and the { token.

  2. Struct values MAY be defined in either single-line or multi-line style.

  3. In the multi-line style, a newline MUST follow the { token, followed by the struct fields indented once, followed by the } token without indentation.

  4. In the single-line style, the { token MUST be followed by a space, followed by the struct fields (each separated by a space following the , token, except for the final field, which MUST omit the , token), then a space and the } token.

  5. Either all fields MUST be qualified, or all fields MUST NOT be qualified with their type.

  6. If a field is qualified, there MUST NOT be a space between the field name and the : token.

  7. There MUST be a space before and after the = token.

Correct examples
let x = struct { x: int = 10i, y: int = 10i };

let x = struct {
     x: int = 10,
     y: int = 10,
};

let x = my_struct {
     x = 10,
     y = 10,
};

Array values

  1. Array values MAY be defined in either single-line or multi-line style.

  2. In the single-line style, there MUST NOT be a space between the [ and ] tokens and the array members.

  3. In the single-line style, a space MUST follow each , token, except for the last.

  4. In the multi-line style, a , must be used after the final token.

  5. In the multi-line style, a newline MUST follow the [ token, and each subsequent line up to but not including the ] token MUST be indented.

  6. In the multi-line style, values may be grouped onto the same line. They must be separated by spaces per rule 3.

  7. When using array initialization shorthand, the ... token MUST NOT be separated from the last value by a space.

Correct examples
let x = [1, 2, 3, 4, 5];
let x = [
     1,
     2,
     3,
     4,
     5,
];
let x = [
     1, 2, 3, 4, 5,
     1, 2, 3, 4, 5,
     1, 2, 3, 4, 5,
     1, 2, 3, 4, 5,
     1, 2, 3, 4, 5,
];
let x: [10]int = [1, 2, 3, 4, 5...];

Tuple values

  1. Tuple values MAY be specified in either a single-line or multi-line style. If the value would fit on a single line within 80 columns, the single-line style must be used.

  2. A tuple value MUST NOT place a space after ( or before ).

  3. In the single-line style, a tuple value MUST NOT place a space before each ,, and MUST place a space after each ,, except for the last, which MUST be omitted.

  4. In the multi-line style, a tuple value MUST NOT place a space before each ,, and MUST place a space after each ,, except for the last, which MUST NOT be omitted.

  5. (subjective) In the multi-line style, the programmer MAY use their discretion to distribute the member value to achieve “balance”.

  6. When breaking to a new line, place the , on the first line. Place the final ) token on the same line as the final members.

Correct examples
let x = (1, 2);
let x = (1, 2,
     3, 4, 5);
let x = (foo(), bar());
let x = (foo(),
     bar(), 1337);

Variables

  1. Variables MUST be named in lower_underscore_case.

  2. If splitting a long binding list onto multiple lines, each line MUST be consistently broken either at the = token or the , token. The breaking token MUST be placed on the first line. An indent MUST precede each continuation line.

Correct examples
let x = 10;
let x: int = 10;
let x: int = 10,
     y: int = 20,
     z: int = 30;
let x: int =
     do_work(lots, of, parameters);

Expressions and operators

  1. Spaces MUST be placed before and after binary operators (e.g. /, &&).

  2. A space MUST NOT be placed between a unary operator (e.g. -, !) and its operand.

  3. A space MUST NOT be placed between the ( and ) operators and the inner expression.

  4. When breaking a long line at a binary operator, the operator SHOULD be placed on the second line.

Correct examples
2 + 4 + 5;
2 + (5 * 10);
-10 * 20;
!foobar;
let x: int = 2
     + 2
     + 2
     + 2;

let x = foo
     && (bar || baz);

Casts

  1. A space MUST NOT be placed between the operand and the : token.

  2. A space MUST be placed between the : token and the type.

Correct example
let x = y: int;

Postfix expressions

Postfix expressions include function calls, array or slice indexing, etc.

  1. Postfix operators MUST NOT be separated from their operands by a space.

Correct examples
func(x, y, z);
list[10];
slice[2..4];
size(int);

Branching expressions

  1. A space MUST NOT be placed between the ( and ) operators and the branch predicate.

  2. If an expression list (i.e. {}) is used, a space MUST be placed between the ) and { tokens, followed by a newline. Each subsequent line MUST be indented, until the } token which MUST be aligned with the first token of the expression.

  3. In for loops, each ; token of the predicate MUST be followed by a space, or a newline.

Correct examples
if (x == y) {
     // ...
};

for (let x = 0; x < 10; x += 1) {
     // ...
};

for (x < y) {
     // ...
};

if (do_work(x) == y
             && do_work(y) == z
             && do_work(z) == q) {
     // ...
};

Match and switch expressions

  1. The preferred style is to align match and switch with their subordinate case branches on the same column.

  2. The body of each case SHOULD be indented an additional level. Generally, => SHOULD be followed by a newline.

  3. If a default case (case =>) is given, it MUST be the last case.

  4. If a case shouldn’t perform any action, the body SHOULD just be void, i.e. case ... => void;. In these cases, the body MAY be on the same line as the case keyword.

  5. (subjective) It is preferred to arrange any terminal cases (i.e. those that return, continue, break, call os::exit or abort(), etc.) before any non-terminal cases. This groups the code which does not terminate closer to the code which logically follows it after the match or switch expression.

Correct examples
match (x) {
case foo =>
     // ...
case foobar =>
     // ...
case foobarbaz =>
     // ...
};

let foobarbaz = match (x) {
case foo =>
     // ...
     yield ...;
case foobar =>
     // ...
     yield ...;
case foobaz =>
     // ...
     yield ...;
};

match (x) {
case (foo | bar) =>
     // ...
case foobar =>
     // ...
case foobaz =>
     // ...
};