Files
IACore/Docs/CODING-STYLE.md
dev0 601b573983
Some checks failed
CI / build-linux-and-wasm (x64-linux) (push) Has been cancelled
Complete Core
2026-01-25 18:26:59 +05:30

10 KiB

IACore - C++ Style Guide

This document defines the coding standards for IACore and all IA Projects.
Philosophy: Enforce Rust-like memory safety and discipline within modern C++20.


1. Oxide Usage

Usage of Oxide library and its philosophy is mandatory.

Oxide is a single header library and a truncated version of it is listed below.

#define OX_UNUSED(v) (void)(v)

namespace Oxide {

// =============================================================================
// Primitive Types
// =============================================================================

using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;

using i8 = std::int8_t;
using i16 = std::int16_t;
using i32 = std::int32_t;
using i64 = std::int64_t;

using f32 = float;
using f64 = double;

using usize = std::size_t;
using isize = std::ptrdiff_t;

// =============================================================================
// Template Types
// =============================================================================

template <typename T> using Const = const T;
template <typename T> using Mut = T;

template <typename T> using Ref = const T &;
template <typename T> using MutRef = T &;
template <typename T> using ForwardRef = T &&;

// =============================================================================
// Memory & Ownership
// =============================================================================

template <typename T> using Box = std::unique_ptr<T>;
template <typename T> using Arc = std::shared_ptr<T>;
template <typename T> using Weak = std::weak_ptr<T>;

template <typename T, typename... Args>
[[nodiscard]] inline auto make_box(ForwardRef<Args>... args) -> Box<T> {
  return std::make_unique<T>(std::forward<Args>(args)...);
}

template <typename T, typename... Args>
[[nodiscard]] inline auto make_arc(ForwardRef<Args>... args) -> Arc<T> {
  return std::make_shared<T>(std::forward<Args>(args)...);
}

// =============================================================================
// Error Handling
// =============================================================================

template <typename T, typename E = std::string>
using Result = Oxide::Internal::Expected<T, E>;

template <typename E> [[nodiscard]] inline auto fail(ForwardRef<E> error) {
  return Oxide::Internal::make_unexpected(std::forward<E>(error));
}

template <typename... Args>
[[nodiscard]] inline auto fail(Ref<std::format_string<Args...>> fmt,
                               ForwardRef<Args>... args) {
  return Oxide::Internal::make_unexpected(
      std::format(fmt, std::forward<Args>(args)...));
}

// =============================================================================
// Utilities
// =============================================================================

namespace Env {
#if defined(NDEBUG)
constexpr Const<bool> IS_DEBUG = false;
constexpr Const<bool> IS_RELEASE = true;
#else
constexpr Const<bool> IS_DEBUG = true;
constexpr Const<bool> IS_RELEASE = false;
#endif
} // namespace Env

[[noreturn]] inline void
panic(Ref<std::string> msg,
      Ref<std::source_location> loc = std::source_location::current()) {
  std::cerr << "\n[panic] " << msg << "\n           At: " << loc.file_name()
            << ":" << loc.line() << "\n";
  std::abort();
}

inline void
ensure(Const<bool> condition, Ref<std::string> msg,
       Ref<std::source_location> loc = std::source_location::current()) {
  if (Env::IS_DEBUG && !condition) {
    std::cerr << "\n[assert] " << msg << "\n            At: " << loc.file_name()
              << ":" << loc.line() << "\n";
    std::abort();
  }
}

using String = std::string;
using StringView = std::string_view;

template <typename T> using Option = std::optional<T>;
template <typename T> using Vec = std::vector<T>;
template <typename T> using Span = std::span<T>;
template <typename T1, typename T2> using Pair = std::pair<T1, T2>;
template <typename T, usize Count> using Array = std::array<T, Count>;

} // namespace Oxide

#define OX_TRY_PURE(expr)                                                     \
  {                                                                            \
    auto _ox_res = (expr);                                                    \
    if (!_ox_res) {                                                           \
      return Oxide::Internal::make_unexpected(std::move(_ox_res.error()));  \
    }                                                                          \
  }

#define OX_TRY(expr)                                                          \
  __extension__({                                                              \
    auto _ox_res = (expr);                                                    \
    if (!_ox_res) {                                                           \
      return Oxide::Internal::make_unexpected(std::move(_ox_res.error()));  \
    }                                                                          \
    std::move(*_ox_res);                                                      \
  })

#define OX_TRY_DISCARD(expr)                                                  \
  {                                                                            \
    auto _ox_res = (expr);                                                    \
    if (!_ox_res) {                                                           \
      return Oxide::Internal::make_unexpected(std::move(_ox_res.error()));  \
    }                                                                          \
    OX_UNUSED(*_ox_res);                                                     \
  }

Note: no need to explicity use Oxide:: prefix as Oxide namespace is almost always globally used in IA projects.

All variables (local, member and parameter/argument) MUST always be wrapped in one of Const<T>, Mut<T>, Ref<T>, MutRef<T> or ForwardRef<T>.

For outer most template of parameters/arguments, use the following Rule to decide between Ref<T> or Const<T>:

  1. Is the parameter size <= 64, then use Const
  2. Otherwise use Ref
Use This Instead of This
Box std::unique_ptr
Arc std::shared_ptr
Weak std::weak_ptr
make_box std::make_unique
make_arc std::make_shared

2. Naming Conventions

We use Rust Naming Conventions to strictly distinguish Types from Values.

Element Style Example
Types / Classes PascalCase StreamReader, TcpSocket
Functions snake_case read_bytes(), create_instance()
Variables snake_case buffer_size, socket_id
Private Members m_snake_case m_parser, m_data_size
Constants SCREAMING_SNAKE MAX_PATH_LENGTH
Namespaces PascalCase IACore, Oxide, IACore::Env
Files PascalCase StreamReader.hpp

Rule: Never use Hungarian notation (e.g., iCount, strName) except for the m_ prefix on private members.


3. Primitives Types

Do not use standard C++ primitives (int, long, unsigned int, double, char).
Use the IACore Rust Aliases defined in IACore.hpp.

Use This Instead of This
i8, i16, i32, i64 int8_t, short, int, long long
u8, u16, u32, u64 uint8_t, unsigned short, unsigned int
f32, f64 float, double
usize size_t, std::size_t
isize std::ptrdiff_t

Exception: Use char only for C-string literals or std::format compatibility.


4. Ownership & Pointers

Adhere to strict ownership semantics. Raw pointers (T*) are non-owning.

  • Box (ia::Box): Exclusive ownership. Use ia::make_box().
  • Arc (ia::Arc): Shared ownership (atomic ref-counting). Use sparingly.
  • Raw Pointers (T*): Borrowed, non-owning, mutable view. Must not be deleted.
  • Const Pointers (const T*): Borrowed, non-owning, read-only view.

Forbidden:

  • new / delete / malloc / free.
  • Passing std::shared_ptr by value (unless sharing ownership is the intent).

5. Error Handling

Exceptions are BANNED.
All fallible functions must return ia::Result.
Correct:

// Returns value OR error string  
auto read_file(const Path& path) -> ia::Result<String>;

Checking Errors (The IA_TRY Macro):
Use IA_TRY to propagate errors (equivalent to Rust's ? operator).

auto process_data() -> Result<String> {  
    IA_TRY(auto data, read_file("test.txt")); // Returns error if read_file fails  
    return process(data);  
}

6. Function Signatures

Use Trailing Return Syntax for all functions (even void returns). This aligns with Rust (fn name() -> type) and handles complex template returns better.

Correct:

auto calculate_sum(u32 a, u32 b) -> u32;
auto do_something() -> void;

Incorrect:

u32 calculate_sum(u32 a, u32 b);
void do_something();

7. Const Correctness and Auto Usage

Immutable by Default.

auto keyword is BANNED.

Variables should be const unless mutation is explicitly required.

Correct:

Const<String> path = "data.bin";
Const<i32> result = process(path);

Incorrect:

String path = "data.bin";
const auto result = process(path);

8. Macros

Avoid Macros.
Use constexpr for constants and inline templates for logic.

  • Allowed: IA_TRY, IA_NODISCARD, Platform detection (internal).
  • Banned: TRUE, FALSE, CONST, IN, OUT.

9. Class Design (The "Safe Wrapper" Pattern)

If a class wraps a C-library or raw resource (like simdjson), it must:

  1. Be Move-Only (Delete Copy Constructor).
  2. Use a Factory Method (create()) that returns Result<Box> or Result.
  3. Never expose raw handles that outlive the wrapper.
class SafeWrapper {  
public:  
    static auto create() -> Result<SafeWrapper>;  
      
    SafeWrapper(SafeWrapper&&) = default;  
    SafeWrapper(const SafeWrapper&) = delete;   
};