Skip to main content
Draconis++ follows specific code style guidelines to maintain consistency across the codebase. All code is formatted using clang-format with a custom configuration.

General rules

  • C++26 standard - Use modern C++ features
  • 2-space indentation - Configured in .clang-format
  • No column limit - Lines can be as long as needed
  • constexpr/consteval - Use where possible for compile-time evaluation
  • noexcept - Mark non-throwing functions
  • [[nodiscard]] - Use for functions whose return value matters

Naming conventions

ElementStyleExample
Types/ClassesPascalCaseSystemInfo, CacheManager
FunctionsPascalCaseGetCpuInfo(), GetMemInfo()
VariablescamelCasecpuCount, memoryUsage
ConstantsSCREAMING_CASEMAX_BUFFER_SIZE, DEFAULT_TIMEOUT
Namespaceslowercasedraconis::core, draconis::utils

Examples

// Type names use PascalCase
struct ResourceUsage {
  u64 usedBytes;
  u64 totalBytes;
};

class CacheManager {
public:
  // Functions use PascalCase
  auto GetCachedValue(StringView key) -> Option<String>;
  auto SetCachedValue(StringView key, String value) -> Result<>;
  
private:
  // Variables use camelCase
  UnorderedMap<String, String> cacheData;
  u32 maxCacheSize;
};

// Constants use SCREAMING_CASE
constexpr u32 MAX_RETRIES = 3;
constexpr f64 TIMEOUT_SECONDS = 5.0;

// Namespaces use lowercase
namespace draconis::core::system {
  auto GetMemInfo(CacheManager& cache) -> Result<ResourceUsage>;
}

Custom type system

Draconis++ uses custom type aliases for improved readability and consistency. Always use these types in new code.

Primitive types

AliasStandard TypeDescription
u8std::uint8_t8-bit unsigned integer
u16std::uint16_t16-bit unsigned integer
u32std::uint32_t32-bit unsigned integer
u64std::uint64_t64-bit unsigned integer
i8std::int8_t8-bit signed integer
i16std::int16_t16-bit signed integer
i32std::int32_t32-bit signed integer
i64std::int64_t64-bit signed integer
f32float32-bit floating-point
f64double64-bit floating-point
usizestd::size_tUnsigned size type
isizestd::ptrdiff_tSigned size type

String types

AliasStandard TypeDescription
Stringstd::stringOwning string
StringViewstd::string_viewNon-owning string view
WStringstd::wstringWide string (Windows)

Container types

AliasStandard TypeDescription
Vec<T>std::vector<T>Dynamic array
Array<T, N>std::array<T, N>Fixed-size array
Span<T>std::span<T>Non-owning view of sequence
Map<K, V>std::map<K, V>Ordered map
UnorderedMap<K, V>std::unordered_map<K, V>Hash map
Pair<T1, T2>std::pair<T1, T2>Pair of values

Smart pointers

AliasStandard TypeDescription
UniquePointer<T>std::unique_ptr<T>Unique ownership
SharedPointer<T>std::shared_ptr<T>Shared ownership

Result types (error handling)

AliasStandard TypeDescription
Option<T>std::optional<T>Value that may be absent
Result<T, E>std::expected<T, E>Value or error
Err<E>std::unexpected<E>Error wrapper for Result
Nonestd::nulloptEmpty Option value

Type usage guidelines

  1. Always use custom types in new code
  2. Import types in .cpp files:
    using namespace draconis::utils::types;
    
  3. Avoid using namespace in header files - use fully qualified names
  4. Prefer Result<T> over exceptions for error handling

Function declarations

Use trailing return type syntax with the auto keyword:
// Use trailing return type
auto GetSystemName() -> Result<String>;

// Mark pure functions as [[nodiscard]]
[[nodiscard]] auto CalculateHash(StringView input) -> u64;

// Use noexcept when appropriate
auto SafeOperation() noexcept -> bool;

// Const member functions
auto GetValue() const -> String;

Error handling

Prefer Result<T> over exceptions for error handling:
// Return Result<T> for operations that can fail
auto ReadFile(StringView path) -> Result<String> {
  std::ifstream file(path);
  if (!file)
    return Err(DracError("Failed to open file"));
  
  String content;
  // Read file...
  return content;
}

// Propagate errors explicitly
auto ProcessData() -> Result<Data> {
  auto content = ReadFile("config.toml");
  if (!content)
    return Err(content.error());
  
  // Process content...
  return processedData;
}

// Use Option<T> for values that may be absent
auto FindUser(StringView username) -> Option<User> {
  auto it = users.find(username);
  if (it != users.end())
    return Some(it->second);
  return None;
}

Helper functions for Result types

// Create an Option with a value
Option<i32> x = Some(42);

// Create an empty Option
Option<i32> y = None;

// Return success from a function returning Result
return Result<String>{"success"};

// Return error from a function returning Result
return Err(DracError("something went wrong"));

Code formatting

Automatic formatting

All code must be formatted using clang-format before submitting:
# Format all source files
just format

# Or use clang-format directly
clang-format -i src include plugins

Key formatting rules

The .clang-format configuration specifies:
  • Based on Chromium style with customizations
  • 2-space indentation
  • No column limit - lines can be as long as needed
  • Attach braces - opening brace on same line
  • Align consecutive assignments and declarations
  • Block indent for arguments
  • Namespace indentation - all contents indented

Include order

Headers are automatically organized by clang-format:
  1. System headers (<vector>, <string>)
  2. Draconis++ headers ("Drac++/Core/System.hpp")
  3. Draconis++ utility headers ("Drac++/Utils/Types.hpp")
  4. Local headers ("LocalFile.hpp")
Example:
#include <string>
#include <vector>

#include "Drac++/Core/System.hpp"
#include "Drac++/Services/Weather.hpp"

#include "Drac++/Utils/Error.hpp"
#include "Drac++/Utils/Types.hpp"

#include "LocalImplementation.hpp"

Best practices

Use constexpr for compile-time values

// Prefer constexpr for compile-time constants
constexpr u32 BUFFER_SIZE = 4096;
constexpr StringView DEFAULT_CONFIG = "config.toml";

// Use constexpr functions when possible
constexpr auto Square(i32 x) -> i32 {
  return x * x;
}

Mark functions nodiscard appropriately

// Functions returning values that shouldn't be ignored
[[nodiscard]] auto GetMemInfo() -> Result<ResourceUsage>;
[[nodiscard]] auto IsValid() const -> bool;

// Functions with side effects don't need nodiscard
auto ClearCache() -> void;  // No nodiscard needed

Use noexcept for non-throwing functions

// Mark functions that never throw
auto GetCachedSize() const noexcept -> usize;
auto Clear() noexcept -> void;

Prefer auto for type deduction

// Use auto with trailing return type
auto GetSystemInfo() -> Result<SystemInfo>;

// Use auto for local variables when type is obvious
auto memInfo = GetMemInfo();
auto count = cpuCores.size();

// Be explicit when type isn't obvious from context
f64 percentage = static_cast<f64>(used) / total;

Running the linter

Check your code with clang-tidy:
just lint

Next steps

Adding platforms

Learn how to add support for new operating systems

Testing

Write and run tests for your changes

Build docs developers (and LLMs) love