Code Style Guide
August 1, 2026 · View on GitHub
Start here next time
- Format all code:
pixi run lint - Quick conventions: 2-space indent, camelCase functions, PascalCase classes
- C++ headers:
.hpp, sources:.cpp, member vars prefixed withm - Python bindings: snake_case functions (PEP 8), PascalCase classes
- See Quick Reference for the full summary
This document describes the code style conventions used in the DART project.
Table of Contents
C++ Style
C++ headers and sources should be contained in the same subdirectory of dart/ that matches their namespace, with the extension .hpp and .cpp, respectively.
C++23 Modern Patterns
DART uses C++23 features to improve code clarity, maintainability, and performance. Use these patterns when appropriate:
Concepts for Template Constraints:
// Prefer concepts over SFINAE
template <std::floating_point T>
T normalize(T value);
// Custom concepts for domain-specific constraints
template <typename T>
concept UniformIntCompatible = std::integral<T> && !std::same_as<T, bool>;
std::span for Function Parameters:
// Prefer std::span over const std::vector<T>&
void addSkeletons(std::span<const SkeletonPtr> skeletons);
// Accepts vectors, arrays, subranges without temporary allocations
Spaceship Operator for Comparisons:
// Replace 6 operators with 2
auto operator<=>(const Ptr& other) const {
return std::tie(mT1, mT2) <=> std::tie(other.mT1, other.mT2);
}
bool operator==(const Ptr& other) const = default;
Branch Prediction Attributes:
// Use [[likely]] / [[unlikely]] for hot paths
if (objects.empty()) [[unlikely]] {
return false;
}
if (!result.isCollision()) [[likely]] {
return;
}
When NOT to use C++23 features:
- Avoid
std::formatuntil Ubuntu 24.04 LTS (GCC 13+) - Don't replace clear index-based loops with complex range expressions
- Keep SFINAE for Eigen compile-time traits (not proper constexpr)
Modernization summary (DART 7): The codebase now assumes a C++23 baseline,
so new read-only APIs should prefer non-owning views (std::span,
std::string_view) and call sites should avoid temporary containers. Favor
std::ranges algorithms, associative contains, and std::erase(_if) when
they clarify intent. Keep modernizations behavior-preserving; avoid deep
algorithm rewrites.
File Naming Conventions
DART now uses snake_case for public headers across the core C++ surface while preserving compatibility via generated PascalCase wrappers.
- Core headers (
dart/): Use snake_case for headers (e.g.,body_node.hpp,collision_group.hpp). PascalCase compatibility headers are generated by CMake for legacy includes and emit deprecation warnings. - Legacy sources (
dart/): Keep existing.cppfile names unless you are performing a targeted migration; avoid mass-renames for sources in this release. - DART 7 simulation modules (
dart/simulation/,tests/unit/simulation/,tests/benchmark/simulation/, and dependents): Use all snake_case file names (e.g.,rigid_body.hpp,free_joint.cpp,test_multibody.py). Build-system files such asCMakeLists.txtkeep their canonical capitalization. - Implementation details: Continue to use the
-implsuffix for template implementations (e.g.,body_node-impl.hpp,rigid_body-impl.hpp). - Tests (
tests/): Use snake_case file names withtest_prefix (e.g.,test_skeleton_state.cpp,test_collision.cpp). This avoids case-collisions on case-insensitive filesystems (macOS, Windows) and aligns with the DART 7 snake_case convention.
Rationale: The public include surface is moving to snake_case while generated PascalCase wrappers keep downstream builds working. All new code uses snake_case consistently.
Quick Reference
- Indentation: 2 spaces
- Functions: camelCase
- Classes: PascalCase
- Enum members: PascalCase (e.g.,
enum class Mode { Disabled, Enabled };) - Member variables: Prefixed with
m(e.g.,mExampleMember) - File extensions:
.hppfor headers,.cppfor sources - File naming: snake_case for all new headers and sources in
dart/andtests/(PascalCase wrappers generated by CMake for headers only); avoids case-collisions on case-insensitive filesystems (macOS, Windows) - Header guards:
#pragma once(macro guards only in vendored code and generated files) - Braces: No "cuddled" braces for functions/classes (namespaces and control flow are exceptions); control-flow bodies are automatically wrapped in braces by
pixi run lint. - Documentation: Doxygen-style comments (
///)
Parameter Naming
- New/modified code: Use camelCase names for function parameters (e.g.,
void setMass(double mass)). - Legacy code: Many functions in
dart/dynamicsstill use_paramconventions. Avoid mass renames—update these opportunistically when touching the surrounding code, and prefer camelCase for any new APIs.
Header Style
Rules:
- Use two-space indentation
- Use camelCase function names
- Use PascalCase class names
- No "cuddled" braces for classes or functions!
- Control-flow bodies are automatically wrapped in braces by
pixi run lint - Use
#pragma onceinstead of macro include guards; it cannot collide and cannot drift when a file is renamed.pixi run check-header-guardsenforces this. Deliberately unguarded files (-impl.hpptextual includes, the poison headers under tests) and vendored upstream code (dart/math/lcp/pivoting/dantzig/,tests/baseline/odelcpsolver/,ikfast.h) are exempt.
Example:
#pragma once
// Place all dependency includes at the top of the file.
// Use absolute paths. Order: Local -> Library -> External -> STL.
#include "dart/common/pointers.hpp"
#include <dart/component_name/ExampleInterface.hpp>
#include <dart/component_name/ExampleOtherInterface.hpp>
#include <library/headers.hpp>
#include <stl_headers>
// Namespaces scopes should be one line each with "cuddled" braces.
namespace dart {
namespace example {
// Use the following macro (defined in dart/common/smart_pointer.hpp) to declare
// STL "smart" pointers. Pointers should be declared ahead of the class so
// that the class itself can use the pointers.
DART_COMMON_DECLARE_SHARED_WEAK(ExampleClass)
/// A required Doxygen comment description for this class. This can be extended
/// to include various useful details about the class, and can use the standard
/// Doxygen tag set to refer to other classes or documentation. It should use
/// the '///' style of block comment.
class ExampleClass : public ExampleInterface, public ExampleOtherInterface
{
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW // Many classes that require Eigen will also need this macro
/// Required brief description of constructor. This will often be as simple as:
/// "Creates an instance of ExampleClass."
///
/// @param[in] foo This is an example parameter description.
/// @param[in] bar This is a longer example parameter description that needs
/// to wrap across multiple lines.
ExampleClass(std::unique_ptr<util::RNG> foo,
const Eigen::Isometry3d& bar = Eigen::Isometry3d::Identity());
ExampleClass(const ExampleClass& other);
ExampleClass(ExampleClass&& other);
ExampleClass& operator=(const ExampleClass& other);
ExampleClass& operator=(ExampleClass&& other);
// If a class should be non-copyable, it should explicitly delete the following:
ExampleClass(const ExampleClass&) = delete;
ExampleClass(ExampleClass&& other) = delete;
ExampleClass& operator=(const ExampleClass& other) = delete;
ExampleClass& operator=(ExampleClass&& other) = delete;
// Classes should explicitly declare a default virtual destructor
// if they do not declare one (unless marking a class as final).
virtual ~ExampleClass() = default;
// Documentation inherited. <-- Use this comment to indicate that the docstring of the interface method applies
int exampleInterfaceFunction() const override; // <-- Always explicitly `override` interface functions without `virtual`
/// Required brief description of method.
/// @note If a method has output parameters, they should be the last
/// arguments.
///
/// @param[in] a A description of a
/// @param[in] b A description of b
/// @param[out] out A description of out
int exampleMethod(int a, int b, int* out) const;
private:
std::unique_ptr<util::RNG> mExampleMember; // Member variables are prefixed with "m"
};
} // namespace example
} // namespace dart
// In certain cases, such as heavily templated code, implementations must be included
// in headers. In this case, a "detail" header should be created in the "./detail"
// subdirectory with the same name as the main header file, but an "-impl" suffix.
// Private declarations in this header can use a "detail" sub-namespace.
#include "dart/component_name/detail/ExampleClass-impl.hpp"
Forward Declarations
Every namespace that needs forward declarations owns exactly one
fwd.hpp (dart/common/fwd.hpp, dart/dynamics/fwd.hpp,
dart/simulation/fwd.hpp, dart/gui/detail/fwd.hpp, ...). Declare a type once
there and #include that header from anywhere that only needs the declaration.
Do not hand-roll namespace dart::x { class Foo; } blocks in consuming headers:
they drift out of sync and hide who owns the type.
// Wrong: a private copy of somebody else's declaration.
namespace dart::simulation::compute {
class MultibodyContactStage;
} // namespace dart::simulation::compute
// Right.
#include <dart/simulation/fwd.hpp>
Two cases legitimately stay in place, and pixi run lint allows them:
- In-file ordering. A primary template whose specializations follow in the
same file, or any entity the same file goes on to define (for example a class
named in a
frienddeclaration before its definition). - File-local tag types that are never defined. These need an entry in
ALLOWLISTinscripts/check_fwd_headers.pywith a comment explaining why.
Third-party namespaces are out of scope, except the render/UI backends used by
dart::gui (Filament, GLFW, Dear ImGui), which are declared once in
dart/gui/detail/backend_fwd.hpp.
Keep a fwd.hpp cheap: declarations and, at most, the export header. A forward
header that pulls in a third-party library brings its names into every consumer
and can change overload resolution there. dart/math/fwd.hpp is the exception
that proves the rule — it includes Eigen and defines the dart::math
Matrix/Vector aliases, so it is a types header, not a declaration header.
That is why dart/math/optimization has its own fwd.hpp for its class
declarations. Add a subfolder fwd.hpp in the same situation.
pixi run check-fwd-headers enforces this; it also runs as part of
pixi run lint and pixi run check-lint.
API Boundary Style
New user-facing API belongs in public module headers and should use the module's
component export macro when the symbol crosses a shared-library boundary
(DART_API, DART_GUI_API, DART_IO_API, etc.). Implementation details belong
under detail/ or internal/; compiled internal symbols should prefer the
matching *_LOCAL macro. See api-boundaries.md before
adding new public headers, exported classes, or Python bindings.
Source Style
Rules:
- Use two-space indentation
- Use camelCase function names
- Use PascalCase class names
- No "cuddled" braces for classes or functions!
- Control-flow bodies are automatically wrapped in braces by
pixi run lint - Each function is separated by an 80 column line of "=" characters
Example:
// Includes should be at the top of the file.
// The first include in a class source file should be the matching `.hpp` header file.
#include "dart/example/ExampleClass.hpp"
#include <stl_headers>
#include <library/headers.hpp>
#include "dart/example/OtherHeaders.hpp"
// Namespace nesting is preferred to "using namespace" directives.
// Namespaces scopes should be one line each with "cuddled" braces.
namespace dart {
namespace example {
// Each function is separated by an 80 column line of "=" characters.
//==============================================================================
int ExampleClass::exampleInterfaceFunction() const
{
if (mExampleMember) {
return 3;
}
return -1;
}
//==============================================================================
int ExampleClass::exampleMethod(int a, int b, int* out) const
{
int result = a + b;
if (out) {
*out = result;
}
return result;
}
} // namespace example
} // namespace dart
Exception Handling
Never swallow exceptions silently. When catching exceptions for logging, always re-throw to propagate the error:
// CORRECT: Log and re-throw
try {
value = std::stod(str);
} catch (const std::exception& e) {
DART_ERROR("Failed to parse '{}': {}", str, e.what());
throw; // Re-throw to propagate the error
}
// WRONG: Silently swallow the exception
try {
value = std::stod(str);
} catch (const std::exception& e) {
DART_ERROR("Failed to parse '{}': {}", str, e.what());
// Missing throw! Execution continues with uninitialized data
}
Rationale: Silently swallowing exceptions can lead to subtle bugs where code continues with uninitialized or partial data. Always propagate errors to the caller so they can handle them appropriately.
Smart Pointers
These guidelines are based on Herb Sutter's article. Consider looking at the full article for details.
General Rules:
- Use a by-value
std::shared_ptras a parameter if the function surely takes the shared ownership. - Use a
const std::shared_ptr&as a parameter only if you're not sure whether or not you'll take a copy and share ownership. - Use a non-const
std::shared_ptr¶meter only to modify thestd::shared_ptr. - Use
std::unique_ptranytime you want to use astd::shared_ptrbut don't need to share ownership. - Otherwise use
Object*instead, orObject&if not nullable.
Macro Conventions
All macros in DART are prefixed with DART_ to distinguish them from other identifiers:
DART_HAS_<optional_dep>: Boolean set to true when an optional dependency is detectedDART_ENABLE_<optional_feature>: Boolean set to true if the optional feature should be enabled when requirements are metDART_ENABLED_<optional_feature>: Boolean set to true if the optional feature is enabled
Use all-caps for all macro names to ensure consistency and to visually distinguish macros from other variables.
Python Style (dartpy bindings)
Naming Conventions
The Python bindings use different naming conventions than the C++ code to follow Python community standards:
| Element | C++ Style | Python Style | Example (C++) | Example (Python) |
|---|---|---|---|---|
| Functions | camelCase | snake_case | isIdentity() | is_identity() |
| Classes | PascalCase | PascalCase | MyClass | MyClass |
| Variables | snake_case | snake_case | my_variable | my_variable |
| Constants | ALL_CAPS | ALL_CAPS | MY_CONSTANT | MY_CONSTANT |
| Namespaces/Modules | N/A | snake_case | dart::dynamics | dart.dynamics |
Example
C++ code:
auto so3 = SO3();
bool is_identity = so3.isIdentity();
Python code:
so3 = SO3()
is_identity = so3.is_identity()
Rationale
Using different naming conventions allows each API to follow its language's community standards:
- camelCase is more common for function names in the C++ community
- snake_case is more common for function names in the Python community
This makes the code more readable and easier to integrate with other projects in each ecosystem, while maintaining consistency within each language.
CMake Style Summary
- Indentation: 2 spaces
- Functions: lowercase
- Variables: ALL_CAPS (except target names)
- Target variables:
target_VARIABLEformat - Quoting: Always quote singleton variables (
"${MY_VAR}"), but not lists
See CONTRIBUTING.md for detailed examples.
Auto-formatting
Use ClangFormat to automatically format C++ code:
cd build/
make check-format # Check without modifying
make format # Apply formatting
Signals to Look For
| Command | Success | Failure |
|---|---|---|
pixi run lint | No errors, check git status for changes | Error messages in output |
make check-format | "All files formatted correctly" | List of files needing formatting |
make format | Files reformatted silently | N/A (always succeeds) |
See Also
- CONTRIBUTING.md - Full style guide with detailed examples
- building.md - Build instructions and CMake configuration
- python-bindings.md - Python bindings architecture