As far as possible, we try to use tools to ensure a consistent style in all languages, and strive to run these tools as CI jobs. New code should follow these rules, and old code should be changed over time to satisfy them.
- Each file should have a well-defined purpose and be concerned with a single narrow concept.
- Add unit tests for new code. As far as possible, one test per source file or logical group of files.
- Names should be as clear and concise as possible, avoiding opaque abbreviations.
We adhere to the pep8 standard, using flake8, pylint and mypy to catch further programming errors or standard violations. Type hints must be used everywhere.
The [.clang-format] file describes the C++ code formatting used in the project. The script
$ scripts/formatcan be run from the repository root to format all files. The CI builds will fail if formatting issues are found.
As well as the above formatting, the following conventions are also used in the C++ code:
- Long comments at the end of lines cause unnecessary line breaks. Comment above the line (do not add empty line between the comment and the line).
- Multi-line comments should use
//style (clang-format does not cope well with other styles).// Your comment here line 1 // line 2 - Do not omit braces for single-line statements in
if,for,while, etc clauses. (Note that clang-format does not enforce this).instead of:// GOOD: if (condition) { x = 0; } else { x = 1; }// BAD: if (condition) x = 0; else x = 1; //can be used at the end of a line in an initializer to retain formatting:libsnark::pb_variable_array<FieldT> value = from_bits( { 0, 0, 1, 0, 1, 1, 1, 1, // <--- these breaks will remain 0, 0, 0, 0, 0, 0, 0, 0, // 0, 0, 0, 0, 0, 0, 0, 0, // 0, 0, 0, 0, 0, 0, 0, 0, // ...- Do not leave whitespace at the end of lines
- Documentation comments should use the
///style, and should be present in the include files ONLY.
- Variables, functions and concrete types (including classes and type aliases) use
snake_case_names. - Macros are
ALL_CAPS. - Functions operating on some type
my_typeshould be namedmy_type_operation, where themy_typeargument appears first. - Type parameters are Pascal or Camel-case with a trailing
T(e.g.typeTorMyTypeT) - Each source file should be concerned with a single "concept" (often single class or type + methods)
- Encodings are named:
jsonhexbytesstring(human-readable string, if different from hex and json)proto(protobuf-defined objects) Note thatheximplicitly means a string type.jsonrefers to strings which contain "whole" json objects, e.g.1234,"string",["array", "of, "strings"]or{"an": "object", "with": "attributes"}.
- Encoding methods are named
to_<encoding>()and static parsing methods are namedfrom_<encoding>(e.g.to_jsonorfrom_proto). - Reading from and writing to streams is performed by methods
std::istream &read_<encoding>(std::istream &)andstd::ostream &write_<encoding>(std::ostream &)(e.g.write_jsonorread_bytes). - Similarly, encoding, parsing, reading and writing functions are named
<type>_to_<encoding>,<type>_from_<encoding>,<type>_read_<encoding>and<type>_write_<encoding>(e.g.point_g1_affine_to_json,point_g1_affine_from_proto). - Conversion between encodings (where there is no high-level type) are named
<source>_to_<dest>(e.g.hex_to_bytes).
- Avoid
autoexcept when strictly necessary. - Use
const(including for local variables) where possible. - Implement streaming and/or encoding methods based on what is sensible for the underlying type. There is no need to for all types to be serializable to all encodings if the rest of the code does not use them.
- In general, pass inputs by (const) reference, and return by value. Implement sensible move operators.