vibe.d beta banner
get vibe.d
0.10.0

Asynchronous I/O that doesn’t get in your way, written in D

Style guide

In general, this guide tries to follow the recommended D style and Phobos conventions. However, there are a number of differences, such as comment style and brace placement inside of functions.

If you want to use a formatting utility, vibe.d's .editorconfig file contains dfmt (included with the compiler) directives that result in a file formatted pretty closely to these guidelines with regards to brace placement and spacing. It doesn't cover all rules and doesn't produce optimal formatting, but provides a decent starting point.

Naming conventions

Modules and packages

RulesRationale

Module and package names are always written lower case. This applies both, to the module declaration, and to the file/directory name. Names usually do not contain any non-alphanumeric characters. An exception to this is when a module name equals that of a D keyword such as function or class. In this case, an underscore is appended to the module name.

A library or application should have all of its source code contained below its own, single root package.

The naming conventions follows the D style conventions and putting everything in one root package avoids conflicting modules and allows to esily map a module to a certain library/application.

module mylib.system.process;
module mylib.compiler.function_;

Type names

RulesRationale

Type names are always written in CamelCase. Acronyms are written all-caps, except if they form a de-facto word that is pronouncible.

Types never have a prefix that characterizes their type family (e.g. interfaces do not have the common I-prefix).

Follows the D/Phobos style conventions.

class Test {}
class HTMLRenderer {} // HTML is an unpronouncible acronym
class JsonProcessor {} // Json is a de-facto word
struct MySimpleStruct {}
interface MyInterface {} // not IMyInterface!
alias int MyInteger;

Functions and methods

RulesRationale

All functions and methods follow a pascalStyle naming scheme. They always begin with a verb.

Follows the D/Phobos style conventions.

int performSomething() {}

struct MyStruct {
	void computeMe() {}
}

void performTemplateComputation(T)(T arg) {}

Variables

Variables have different naming conventions according to their place of definition and according to their level of protection. This is to make it obvious with which kind of variable is dealt with in a certain piece of code, so that the code is easy to understand and looking up or changing the variable declaration is made simple. Variables never begin with a verb to make them distinguishable from functions and methods.

Global variables

RulesRationale

The naming scheme of global variables depends on their shared state. Their basic naming scheme is pascalCase. Global thread-local variables get a g_ prefix. Variables with a shared or __gshared storage class get a gs_ prefix. Public variables may drop the prefix, but properties should be preferred in this case.

The semantic prefix makes it possible to immediately distinguish the scope of different variables, as well as their need for synchronization. Encountering use of a shared variable with no synchronization/atomic operation in place can be directly spotted.

private int g_moduleLevelVariable;
package int g_packageLevelVariable;
public int g_publicVariable;
public int publicVariable; // possible, but @property recommended
shared int gs_globalSharedVariable;
__gshared int gs_globalSharedVariable2;

Function parameters

RulesRationale

Parameters use underscore_naming_convention. They should have meaningful names without abbrevations, if practical. Remember that these will show up in the API documentation later on.

The use of a lower-case naming convention enhances the visual separation of function name and signature and allows to distinguish local variables from fields or global variables immediately.

void myFunction(int first_parameter, int second_parameter, int next) {}

Local variables

RulesRationale

Local variable names should stay short (use abbrevations) and contain neither underscores, nor upper case letters. The abbrevations must stay clear and one-letter variable names such as i, j, k should only be used as loop indices according to their nesting level.

Using this convention makes local variables distinguishable from parameters and public fields in most cases. Since local variables are expected to show up very frequently in the code, the short nature of their name helps to keep the code concise and readable.

void func()
{
	int idx = 0;
	int maxvalue = 0;
	char[] outbuf; // abbrevated to keep the name short
	foreach (i; 0 .. 10)
		maxvalue += i;
}

Fields and properties

RulesRationale

Struct/class variables and properties in general follow the pascalCase naming convention. Private and protected class/struct member variables have an m_ prefix. Public variables and properties have no prefix. Accessing public fields or properties must always use a a this prefix if the field is within the same object instance.

The prefix makes member variables immediately recognizable as fields and this avoids the need to use `this.` as a prefix to distinguish them from other variables optically. Public fieds still require that prefix so they can be distinguished from parameters and local variables without searching the declaration.

class MyClass {
	private {
		int m_myField;
	}
	
	protected {
		int m_myField2;
	}
	
	int somePublicField; // public fields have no prefix
	
	@property int myField() const { return m_myField; }
	
	void doSomething()
	{
		this.somePublicField = m_myField;
		m_myField2 = this.myField;
	}
}

Punctiation

Braces

RulesRationale

There are three kinds of brace placement styles used:

  1. Opening brace at a separate line with a block of code in-between

    • functions and methods
  2. Opening brace at the end of the first line with a block of code following - the opening brace must be preceeded by a space character

    • struct
    • class
    • enum
    • private and protected blocks
    • control statements (if, for, switch etc.)
  3. Opening brace and closing brace at the end of the first line - the opening brace must be preceeded by a space character

    • trivial function bodies
    • trivial bodies of control structures

Consecutive control statements of the same logical chain are appended directly to the same line as the preceeding closing brace.

Cases 2 and 3 are supposed to increase vertical conciseness in places where it usually benefits readability. Case 1 on the other hand sets apart the function signature from the body to improve the visual distinction of declaration and implementation when skimming over the source code.

private {
	int s_globalCounter;
}

class C {
	private {
		int m_field;
	}
	
	@property int field() const { return m_field; }
	
	void doSomething()
	{
		try {
			foreach (i; 0 .. 10) {
				// ...
			}
		} catch (Exception e) {
			// ...
		} finally {
			// ...
		}
		
		if (1 == 2) {
			// ...
		} else {
			// ...
		}
	}
}

Clamps

RulesRationale

In general, the inside of clamps tightly fits its contents. For function declarations and function calls, there is also no space before the opening clamp. Control statements have a single space before the opening clamp.

Tight clamps make the code more concise and help grouping related content together visually. Since control statements often contain large expressions with logical operators inside, a space is added to visually separate the expression from the rest of the line, resulting in better visual cues about it.

int doSomething(int a, int b, int c)
{
   assert (b > 0); // maybe assert should be an exception?
   
	if (a > 0) doSomething(a - b, a * (c + a), c);
	
	switch (b) {
		default: return b+c;
		case 1: return 0;
		case 2: return -1;
	}
}

Operators

RulesRationale

Binary operators are usually surrounded by spaces, except in cases where it enhances readability to leave them off. Unary operators are always directly preceeding or following their argument. The ".." range token is always surrounded by spaces.

int count = 0;
foreach (i; 0 .. 10) {
	if (i / 2 > 3) doSomething(count++);
	else doSomething(sum(array[i .. i+1]));
}

Order of declaration

The general intention for the declaration order as specified here is to be as helpful as possible in reading/scanning the source code as a user of the code and also, as a secondary goal, as the developer of the code.

Module level

RulesRationale

The general order of declaration is sorted by protection level: publicprotectedpackageprivate

Inside of these sections it is recommended to put functions first, followed by type definitions. The declaration order then generally goes from high level to low level (i.e. a function which uses some other functions comes before the functions that it depends on).

Class/struct level

RulesRationale

The following order must be adhered to:

  1. private fields
  2. protected fields
  3. public fields
  4. constructors and finalizers
  5. static functions
  6. public properties
  7. public functions
  8. protected functions
  9. package functions
  10. private functions

Other combinations (e.g. private properties or package fields) are generally discouraged, but, if used, should be placed according to their protection attribute.

This order results from the following goals:

  • Group all fields together to provide a complete overview and reduce the chance to overlook a field.
  • List the members generally in the order of most important to least important
  • Separate state (fields, properties) from actions (methods)

Function level

Inside of functions, there is generall no restriction on the order of declarations. However, it is recommended to place inner functions and types, as well as static variables at the top of the function. Variable declarations should occur just before their first use or at the latest possible earlier point.

White space

RulesRationale

Basic indentation always uses tab characters and no spaces. Indentation is always semantic, meaning that each nesting level gets exactly one tabulator of indentation. When braking up a statement or a declaration into multiple lines, indent all but the first line by one additional tab.

Everything else, and in particular aligning comments on different lines to the same column, is done with spaces and no tabs.

Function declarations are separated by one or two blank line and inside of functions a single blank line should be used to separate different functional/logical blocks (e.g. a loop from other statements).

Intendation is probably the topic that is mostly argued about. The rule used here is pragmatic in that it allows different people to set different tab widths, but still guanrantees that manual alignment works correctly.

enum MyEnum {↓
→   someMember,······/// Aligned comment↓
→   someOtherMember··/// Another aligned comment↓
}↓
↓
foreach (i; 0 .. 10) {↓
→   makeSomethingThatCausesAnOverlongLine(i, i+1,↓
→   →   i, i*2 + 10, "some param");↓
}↓

Doc comments

RulesRationale

Documentation comments come in three basic forms (see example). Usually the same style should be used throughout all documented members of an entity, but exceptions may be made in the benefit of readability. In general, very short documentation strings (e.g. for enum members) should use the single line style and all other documentation should use the block style. The special "ditto" comment must always be a single line of the form /// ditto.

The module header must always contain the "Authors", "Copyright" and "License" sections, as given in the example.

/**
	This is the short description for the module.
	
	This is the long description of the module that gives an introduction to the
	module as a whole. This is optional.
	
	Copyright: © 2020 Sönke Ludwig
	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
	Authors: Some Name, Some Other Name
*/
module vibe.some.module;

/**
	This is a class.
*/
class Class {
	/** This function does something.
	
		Params:
			param1 = Maybe this parameter has an effect
			param2 = Maybe this one has also an effect
			
		Returns:
			The result of doing something is returned.
	*/
	int doSomething(int param1, int param2);
	
	/// ditto
	int doSomething();
}