Read the standards and stick to them! There is some automation to help with this, but the automation is far from comprehensive.
Here are the types of automation that should help stick to the standard:
- The VSCode workspace file
circuits.code-workspace
is configured to automatically format your code nicely when you save a C++ file.- It uses
cpp/.clang-format
for this
- It uses
- These workspace settings are also configured to warn you (with yellow squiggles) when something does not follow some of the rules.
- It uses
cpp/.clangd
for this
- It uses
- A tidy check is run in CI
- Job fails if your code would be changed by
./scripts/tidy.sh fix
- Job fails if your code would be changed by
- To perform some auto-tidying of your code, run
./scripts/tidy.sh fix
fromcpp/
- Commit your code first since tidying will occasionally mess up your code!
- This will run
clang-tidy
on all C++ source files- It uses
cpp/.clang-tidy
* and formats tidied code withcpp/.clang-format
- It uses
- Manually review any fixes to your code!
- If you disagree with an auto-fix or if it is buggy, use
// NOLINT...
(more here) - If you believe we should reject an entire class of tidy fixes, consider explicitly omitting from our checks or errors in
./clang-tidy
- Discuss with others first
- If you disagree with an auto-fix or if it is buggy, use
- Note: tidying takes a while!
- You may need to run this multiple times!
- An error (with an auto-fix) in one round may prevent certain subsequent fixes
- A fix in the one round may introduce more potential for fixes
- general
- when something is not covered below, fall back to Google's style guide
- all TODOs should mention a username, email or bug number
// TODO(dbanks12) // TODO([email protected]) // TODO(bug 12345)
- if your editor warns you about a line of code fix it!
- consider doing so even if you didn't write that code
- spacing
- 4 spaces except for access-specifiers (
public
/protected
/private
) which can use 2 - namespaces are not indented
- for continued indentation, just be sane
- remove trailing spaces at end of line (use a plugin)
- include a newline at the end of a file
namespace my_namespace { class MyClass { public: // ... public stuff protected: // ... protected stuff private: // ... private stuff void my_private_function0(int arg0, int arg1) { // ... } void my_private_function1( int arg0, int arg1) { // ... } } } // namespace my_namespace
- 4 spaces except for access-specifiers (
- braces
- functions use curly brace alone on newline
- namespaces, classes, structs, enums, ifs, and loops use curely brace on same line
- examples
void my_function() { // ... } for (int i = 0; i < max; i++) { // ... } if (something_is_true) { // ... } struct MyStruct { // ... }
- naming
snake_case
for files, namespaces and local variables/membersCamelCase
for classes, structs, enums, types- exceptions types can be made for types if trying to mimic a std type's name like
uint
orfield_ct
- exceptions types can be made for types if trying to mimic a std type's name like
ALL_CAPS
forconstexpr
s, global constants, and macros- do use
- Clear names
- Descriptive names
- don't use
- abbreviations
- words with letters removed
- acronyms
- single letter names
- Unless writing maths, in which case use your best judgement and follow the naming of a linked paper
auto
- include
*
,&
, and/orconst
even when usingauto
- use when type is evident
- use when type should be deduced automatically based on expr
- use in loops to iterate over members of a container
- don't use if it makes type unclear
- don't use you need to enforce a type
- examples
auto my_var = my_function_with_unclear_return_type(); // BAD auto my_var = get_new_of_type_a(); // GOOD
- include
const
andconstexpr
- use
const
whenever possible to express immutability - use
constexpr
whenever aconst
can be computed at compile-time - place
const
/constexpr
BEFORE the core type as is done in bberg stdlib - examples
const int my_const = 0; constexpr int MY_CONST = 0;
- use
namespace
andusing
- never do
using namespace my_namespace;
which causes namespace pollution and reduces readability - avoid doing
typedef my::OldType NewType
and instead dousing NewType = my::OldType
- namespaces should exactly match directory structure. If you create a nested namespace, create a nested directory for it
- example for directory
aztec3/circuits/abis/private_kernel
:namespace aztec3::circuits::abis::private_kernel { // ... } // namespace aztec3::circuits::abis::private_kernel
- use
init.hpp
only for core/critical renames likeNT/CT
and for toggling core types likeComposer
- use unnamed/anonymous namespaces to import and shorten external names into just this one file
- all of a file's external imports belong in a single anonymous namespace
namespace { ...\n } // namespace
at the very top of the file directly after#include
s - use
using Rename = old::namespace::prefix::Name;
to import and shorten names from external namespaces - avoid using renames to obscure template params (
using A = A<NT>;
) - never use renames to remove the
std::
prefix - never use renames to remove a
NT::
orCT::
prefix
- all of a file's external imports belong in a single anonymous namespace
test.cpp
tests must always explicitly import every single name they intend to use- they might want to test over multiple namespaces, native and circuit types, and composer types
- avoid calling barretenberg's functions directly and instead go through interface files like
circuit_types
andnative_types
using
statements should be sorted case according to theLexicographicNumeric
rules- see the
SortUsingDeclarations
section of the LLVM Clang Format Style Options document
- see the
- if your IDE is telling you that an include or name is unused in a file, remove it!
- never do
- includes
- start every header with
#pragma once
index.hpp
should include common headers that will be referenced by most cpp/hpp files in the current directoryinit.hpp
should inject ONLY critical renames (likeNT
/CT
) and type toggles (like Composer)- example
using NT = aztec3::utils::types::NativeTypes;
- example
- avoid including headers via relative paths (
../../other_dir
) unless they are a subdir (subdir/header.hpp
)- use full path like
aztec3/circuits/hash.hpp
- use full path like
- ordering of includes
- this source file's header
- essentials (if present)
"index.hpp"
"init.hpp"
- headers nearby
- headers from this directory (no
/
) - headers from this project using relative path (
"private/private_kernel_inputs.hpp"
) - Note: headers in this group are sorted in the above order (no
/
first, relative paths second)
- headers from this directory (no
- headers from this project using full path (starts with aztec3:
"aztec3/constants.hpp"
) - barretenberg headers
<gtest>
or other third party headers specified in.clang-format
- C++ standard library headers
- use quotes internal headers
- use angle braces for std library and external library headers
- this includes barretenberg
- each group of includes should be sorted case sensitive alphabetically
- each group of includes should be newline-separated
- example:
#include "this_file.hpp" #include "index.hpp" #include "init.hpp" #include "my_file_a_in_this_dir.hpp" #include "my_file_b_in_this_dir.hpp" #include "other_dir/file_a_nearby.hpp" #include "other_dir/file_b_nearby.hpp" #include "aztec3/file_a_in_project.hpp" #include "aztec3/file_b_in_project.hpp" #include <barretenberg/file_a.hpp> #include <barretenberg/file_b.hpp> #include <gtest> #include <vector> #include <iostream>
- start every header with
- access specifiers
- order them
public
,protected
,private
- order them
- struct and array initialization
- use
MyStruct my_inst{};
- will call default constructor if exists and otherwise will value-initialize members to zero (or will call their default constructors if they exist)
- explicitly initialize struct members with default values:
NT::fr my_fr = 0;
- initialize arrays using
std::array<T, 8> my_arr{};
- this value-initializes all entries to 0 if T has no default constructor, otherwise calls default constructor for each
- arrays of fields/
fr
are different! Usezero_array
helper function for initializationstd::array<fr, VK_TREE_HEIGHT> vk_path = zero_array<fr, VK_TREE_HEIGHT>();
- This is necessary because
fr
does have a default constructor that intentionally does not intialize its members to 0
- use
- references
- use them whenever possible for function arguments since pass by reference is cheaper
- make arg references "const" if they should not be modified inside a function
- avoid C-style coding
- avoid
malloc/free
- use
std::array/vector
instead ofint[]
- use references instead of pointers when possible
- if pointers are necessary, use smart pointers (
std::unique_ptr/shared_ptr
) instead of raw pointers - avoid C-style casts (use
static_cast
orreinterpret_cast
)
- avoid
- comments
- use doxygen docstrings (will include format example)
/** * @brief Brief description * @details more details * @tparam mytemplateparam description * @param myfunctionarg description * @return describe return value * @see otherRelevantFunction() * @see [mylink](url) */
- every file should have a meaningful comment
- every class/struct/function/test should have a meaningful comment
- class/struct comment might == file comment
- comment function preconditions ("arg x must be < 100")
- use doxygen docstrings (will include format example)
- side-effects
- avoid functions with side effects when it is easy enough to just have pure functions
- if a function modifies its arguments, it should be made very clear that this is happening
- same with class methods that modify members
- function arguments should be
const
when they will not be modified
- global state
- no
- docs
- every subdir should have a readme
- functions
- use
[[nodiscard]]
if it makes no sense to call this function and discard return value[[nodiscard] int my_function() { // ... return some_int; } // later can't do my_function(); // can only do int capture_ret = my_function();
- if there is a name clash, prefix parameter with underscore like
_myparam
void my_function(int _my_var) { my_var = _my_var; }
- use
- macros
- avoid macros as much as possible with exceptions for
- testing
- debug utilities
- agreed upon macro infrastructure (like cbinds)
- avoid macros as much as possible with exceptions for
- misc
- use
uintN_t
instead of a primitive type (e.g.size_t
) when a specific type width must be guaranteed - avoid signed types (
int
,long
,char
etc) unless signedness is required- signed types are susceptible to undefined behavior on overflow/underflow
- initialize pointers to
nullptr
- constructors with single arguments should be marked
explicit
to prevent unwanted conversions - if a constructor is meant to do nothing, do
A() = default;
instead ofA(){}
(explanation here)- definitely don't do
A(){};
(with semicolon) which can't even be auto-fixed byclang-tidy
- definitely don't do
- explicitly use
override
when overriding a parent class' member (explanation here) - avoid multiple declarations on the same line
- do:
int a = 0; int b = 0;
- dont:
int a, b = 0, 0;
- do:
- use
std::vector::emplace_back
instead ofpush_back
- don't use
std::make_pair
when usingemplace_back
(unnecessary as explained here)
- don't use
- no magic numbers even if there is a comment explaining them
- use