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>orConst<T>:
- Is the parameter size <= 64, then use
Const- 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:
- Be Move-Only (Delete Copy Constructor).
- Use a Factory Method (create()) that returns Result<Box> or Result.
- Never expose raw handles that outlive the wrapper.
class SafeWrapper {
public:
static auto create() -> Result<SafeWrapper>;
SafeWrapper(SafeWrapper&&) = default;
SafeWrapper(const SafeWrapper&) = delete;
};