aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CODESTYLE.md (renamed from docs/code_style.md)291
1 files changed, 142 insertions, 149 deletions
diff --git a/docs/code_style.md b/CODESTYLE.md
index f417a047..4401978a 100644
--- a/docs/code_style.md
+++ b/CODESTYLE.md
@@ -1,41 +1,45 @@
# TeachOS C++ Code Style Guide
This document codifies the C++ coding idioms used throughout the TeachOS kernel.
-It covers language usage, ownership and lifetime models, algorithm selection, error
-propagation, class design, and naming conventions. It does **not** cover token-level
-formatting, which is enforced automatically by the `.clang-format` configuration.
+It covers language usage, ownership and lifetime models, algorithm selection, error propagation, class design, and naming conventions.
+It does **not** cover token-level formatting, which is enforced automatically by the `.clang-format` configuration.
---
## 1. Language Standard and Vocabulary
-TeachOS targets **C++23** without compiler extensions (`CMAKE_CXX_EXTENSIONS NO`).
-Standard library features may be used freely where a hosted implementation is
-available (i.e., in `libs/`, `kapi/`, and `kernel/`), but the project ships its own
-standard library subset in `libs/kstd/`. **kstd types must be preferred over their
-`std::` equivalents** wherever a kstd equivalent exists.
-
-| Concept | Preferred | Avoid |
-|---|---|---|
-| Dynamic array | `kstd::vector<T>` | `std::vector<T>` |
-| String (owning) | `kstd::string` | `std::string` |
-| String view | `std::string_view` | (kstd has no alias; use `std::string_view` directly) |
+TeachOS targets **C++23** without compiler extensions.
+Standard library features may be used freely in tests only.
+All other parts of the codebase rely on a freestanding variant of the standard library as shipped with the toolchain.
+Types that are not part of the toolchain's freestanding standard library are provided by the `kstd` library.
+Below is an overview of `kstd` replacements to be used in the **non-test** kernel code, including the bundled support libraries.
+This list is does not claim completeness.
+
+| Concept | Preferred | Replaces |
+|--------------------|-------------------------|---------------------------------------|
+| Dynamic array | `kstd::vector<T>` | `std::vector<T>` |
+| Strings | `kstd::string` | `std::string` |
| Non-owning pointer | `kstd::observer_ptr<T>` | raw `T*` for ownership-neutral access |
-| Shared ownership | `kstd::shared_ptr<T>` | `std::shared_ptr<T>` |
-| Unique ownership | `kstd::unique_ptr<T>` | `std::unique_ptr<T>` |
-| Failable results | `kstd::result<T>` | exceptions, output parameters, `std::optional` for errors |
-| Printing | `kstd::println(...)` | `std::println(...)`, `printf` |
+| Shared ownership | `kstd::shared_ptr<T>` | `std::shared_ptr<T>` |
+| Unique ownership | `kstd::unique_ptr<T>` | `std::unique_ptr<T>` |
+| Printing | `kstd::println(...)` | `std::println(...)`, ... |
+| String Formatting | `kstd::format(...)` | `std::format(...)`, ... |
-`std::string_view`, `std::span`, `std::array`, `std::optional`, `std::byte`, and the
-`std::ranges` and `std::views` namespaces are used directly from the standard library
-because they have no kstd equivalents.
+Common standard library parts that are available in the freestanding implementation include but are not limited to:
+- `std::string_view`
+- `std::span`
+- `std::array`
+- `std::optional`
+- `std::byte`
+- `std::ranges`
+- `std::views`
----
+These parts may be used freely throughout the codebase.
## 2. Function Declarations — Trailing Return Types
-**All** functions and member functions use trailing return type syntax, including those
-returning `void`.
+**All** functions and member functions use trailing return type syntax, including those returning `void`.
+See the following code snippet for examples:
```cpp
// Correct
@@ -49,11 +53,8 @@ bool bitmap_is_set(std::span<std::byte const> bitmap, std::size_t index);
void init();
```
-This rule applies to: free functions, member functions, lambdas with explicit return
-types, and virtual functions. The only exception is constructors and destructors,
-which have no return type at all.
-
----
+This rule applies to: free functions, member functions, lambdas with explicit return types, and virtual functions.
+The only exception is constructors and destructors, which have no return type at all.
## 3. Parameter Passing Conventions
@@ -61,31 +62,36 @@ The choice of passing convention encodes intent and must be consistent.
### 3.1 View and cheaply copyable types — pass by value
-Types that are designed to be non-owning views or are trivially copyable must be
-passed and returned **by value**. Passing them by `const &` is redundant and adds
-a pointer indirection with no benefit.
+Types that are designed to be non-owning views or are trivially copyable must be passed and returned **by value**.
+Passing them by `const &` is redundant and adds a pointer indirection with no benefit.
-This applies to:
+This includes but is not limited to:
- `std::string_view`
- `std::span<T>`
- `kstd::observer_ptr<T>`
-- `kstd::bytes`, `kstd::pages` and similar unit wrappers
+- `kstd::bytes` and similar unit types
- `kapi::capabilities::facet_id`
-- `kapi::memory::page`, `kapi::memory::frame`, `kapi::memory::physical_address`, `kapi::memory::linear_address`
+- `kapi::memory::page`
+- `kapi::memory::frame`
+- `kapi::memory::physical_address`
+- `kapi::memory::linear_address`
+
+See the following code snippet for examples:
```cpp
// Correct
-auto resolve(kapi::capabilities::facet_id id, std::string_view name) -> void *;
+auto resolve(kapi::capabilities::facet_id id, std::string_view name) -> std::observer_ptr<void>;
auto has(std::span<std::byte const> data) -> bool;
// Wrong
-auto resolve(kapi::capabilities::facet_id const & id, std::string_view const & name) -> void *;
+auto resolve(kapi::capabilities::facet_id const & id, std::string_view const & name) -> std::observer_ptr<void>;
```
-### 3.2 Large or non-trivial types — pass by `const &`
+### 3.2 Large or non-trivially copyable types — pass by `const &`
+
+For types that own heap memory or are non-trivially copyable, use `const &` when the callee does not take ownership.
-For types that own heap memory or are non-trivially copyable, use `const &` when the
-callee does not take ownership.
+See the following code snippet for examples:
```cpp
auto do_publish(kstd::string const & name) -> kstd::result<void>;
@@ -94,13 +100,14 @@ auto add_child(kstd::string const & child_name) -> void;
### 3.3 Sink parameters — pass by value and move
-When a function is designed to take **ownership** of an argument, accept it by value
-and move it into its destination. This makes the transfer explicit at the call site.
+When a function is designed to take **ownership** of an argument, accept it by value and move it into its destination.
+This makes the transfer explicit at the call site.
+
+See the following code snippet for examples:
```cpp
// In the header
auto add_child(kstd::shared_ptr<device> child) -> void;
-auto do_publish(kstd::shared_ptr<device> device, kstd::string name, ...) -> kstd::result<void>;
// In the implementation
auto bus::add_child(kstd::shared_ptr<device> child) -> void
@@ -111,28 +118,32 @@ auto bus::add_child(kstd::shared_ptr<device> child) -> void
### 3.4 Mutable subsystem references — pass by non-const reference
-Services and subsystems that are mutated in-place (e.g., `page_mapper &`,
-`driver_state &`, `kapi::devices::bus &`) are passed by non-const reference. This
-expresses that the function operates on a shared, mutable context.
+Services and subsystems that are mutated in-place (e.g., `page_mapper &`, `driver_state &`, `kapi::devices::bus &`) are passed by non-const reference.
+This expresses that the function operates on a shared, mutable context.
+
+See the following code snippet for examples:
```cpp
auto remap_kernel(kapi::memory::page_mapper & mapper) -> void;
auto add_directory_entry(inode & directory, inode & child, driver_state & state, write_batch & batch) -> kstd::result<void>;
```
----
-
## 4. Error Handling
+TeachOS kernel code, and all library code used by it, cannot make use of exceptions.
+Use of exception related keywords, `try`, `catch`, `throw` in kernel code will cause compilation to fail.
+However, exceptions are allowed in test code.
+
### 4.1 Recoverable errors — `kstd::result<T>`
-Functions that can fail in an expected, recoverable way must return `kstd::result<T>`
-(an alias for `std::expected<T, kstd::error_code>`). Use the `kstd::success()` and
-`kstd::failure()` helpers consistently.
+Functions that can fail in an expected, recoverable way must return `kstd::result<T>`.
+Use the `kstd::success()` and `kstd::failure()` helpers consistently.
+Define per-subsystem error code if necessary.
+
+See the following code snippet for examples:
```cpp
-auto mount(kstd::shared_ptr<inode> parent)
- -> kstd::result<std::pair<kstd::shared_ptr<inode>, state *>>;
+auto mount(kstd::shared_ptr<inode> parent) -> kstd::result<std::pair<kstd::shared_ptr<inode>, state *>>;
// In the implementation
if (!device)
@@ -144,6 +155,8 @@ return kstd::success(result_value);
Callers must check the result before using its value. The idiomatic check is:
+See the following code snippet for examples:
+
```cpp
auto result = some_function();
if (!result)
@@ -153,17 +166,18 @@ if (!result)
// use *result
```
-Monadic composition (`transform`, `and_then`, `or_else`) is preferred over
-manual if-check-and-return chains when it produces clearer code.
+Monadic composition (`transform`, `and_then`, `or_else`) is preferred over manual if-check-and-return chains when it produces clearer code.
### 4.2 Unrecoverable errors — `kapi::system::panic`
Violations of kernel invariants (e.g., a subsystem used before being initialized,
-OOM during boot) call `kapi::system::panic(...)`. `panic` is `[[noreturn]]`. It must
-not be used for recoverable errors.
+OOM during boot) call `kapi::system::panic(...)`.
+`panic` is `[[noreturn]]`.
+It must not be used for recoverable errors.
+
+Log prefix convention: `[SUBSYSTEM:TAG] message`. Examples: `[OS:DEV]`, `[OS:VFS]`, `[ARCH:DRV]`.
-Log prefix convention: `[SUBSYSTEM:TAG] message`. Examples: `[OS:DEV]`,
-`[OS:VFS]`, `[ARCH:DRV]`.
+See the following code snippet for examples:
```cpp
if (!instance)
@@ -172,53 +186,40 @@ if (!instance)
}
```
-### 4.3 No exceptions
-
-The kernel does not use C++ exceptions. Do not write `throw` or `try`/`catch` in
-kernel code.
-
----
-
## 5. Ownership and Lifetime
### 5.1 Shared ownership — `kstd::shared_ptr`
-Use `kstd::shared_ptr<T>` when a resource is co-owned by multiple subsystems and
-its lifetime must be extended by any of them (e.g., `device`, `inode`, `dentry`).
+Use `kstd::shared_ptr<T>` when a resource is co-owned by multiple subsystems and its lifetime must be extended by any of them (e.g., `device`, `inode`, `dentry`).
Weak back-references that must not extend lifetime use `kstd::weak_ptr<T>`.
### 5.2 Non-owning references — `kstd::observer_ptr` and raw references
-Use `kstd::observer_ptr<T>` to express a non-owning pointer where null is a valid
-state and the holder has no say in the lifetime of the target. Use a raw reference
-(`T &` or `T const &`) when null is not valid and the reference is short-lived (i.e.,
-a function parameter or a local alias).
+Use `kstd::observer_ptr<T>` to express a non-owning pointer where null is a valid state and the holder has no say in the lifetime of the target.
+Use a raw reference (`T &` or `T const &`) when null is not valid and the reference is short-lived (i.e. a function parameter or a local alias).
-Never use raw `T *` to mean "sometimes I own this, sometimes I don't". Ownership must
-be expressed unambiguously through the pointer type.
+Never use raw `T *` to mean "sometimes I own this, sometimes I don't".
+Ownership must be expressed unambiguously through the pointer type.
-### 5.3 Driver data — `kstd::shared_ptr<void>`
+### 5.3 Device driver data — `kstd::shared_ptr<void>`
-Drivers attach their state to a `device` using an untyped `kstd::shared_ptr<void>`
-via `device::set_driver_data`. A driver retrieves its state via `device::driver_data`.
-This allows the device tree to destroy driver data automatically when the device is
-released, without the device knowing the concrete driver type.
-
----
+Drivers attach their state to a `device` using an untyped `kstd::shared_ptr<void>` via `device::set_driver_data`.
+A driver retrieves its state via `device::driver_data`.
+This allows the device tree to destroy driver data automatically when the device is released, without the device knowing the concrete driver type.
## 6. Algorithm and Range Usage
### 6.1 Prefer `std::ranges` algorithms over manual loops
-When processing a range to search, filter, transform, or reduce, use the appropriate
-`std::ranges` algorithm or view pipeline instead of writing a raw `for` loop.
+When processing a range to search, filter, transform, or reduce, use the appropriate `std::ranges` algorithm or view pipeline instead of writing a raw `for` loop.
+
+See the following code snippet for examples:
```cpp
// Correct
-auto already_published = std::ranges::any_of(
- m_entries, [&](auto const & entry) {
- return entry.id() == id && entry.device().get() == device.get();
- });
+auto already_published = std::ranges::any_of(m_entries, [&](auto const & entry) {
+ return entry.id() == id && entry.device().get() == device.get();
+});
std::ranges::for_each(observers, [&](auto observer) { /* ... */ });
@@ -234,16 +235,15 @@ return false;
```
Acceptable uses of explicit loops include:
-- Accumulation or mutation that modifies state in-place and cannot be cleanly
- expressed with a ranges algorithm.
+- Accumulation or mutation that modifies state in-place and cannot be cleanly expressed with a ranges algorithm.
- Low-level routines dealing with raw memory arithmetic (allocators, page mappers).
- Iterator-pair loops in library internals (`kstd::vector`, `kstd::basic_string`).
### 6.2 Prefer view composition over intermediate containers
-Build processing pipelines using `std::views::filter`, `std::views::transform`,
-`std::views::reverse`, `std::views::split`, and `std::ranges::subrange` rather than
-materialising intermediate vectors.
+Build processing pipelines using `std::views::filter`, `std::views::transform`, `std::views::reverse`, `std::views::split`, and `std::ranges::subrange` rather than materialising intermediate vectors.
+
+See the following code snippet for examples:
```cpp
// Correct
@@ -257,8 +257,8 @@ auto modules_view = std::ranges::subrange(begin(), end())
### 6.3 Do not call the same function twice to avoid storing the result
-If an intermediate value is needed more than once, store it in a local variable. This
-applies especially to factory calls and heap allocations.
+If an intermediate value is needed more than once, store it in a local variable.
+This applies especially to factory calls and heap allocations.
```cpp
// Wrong — double invocation, two allocations, different objects
@@ -277,14 +277,13 @@ for (auto driver : descriptors)
}
```
----
-
## 7. Class and Struct Design
### 7.1 Prefer `struct` over `class`
The entire codebase uses `struct` for all type definitions with explicit `private:`
-sections where necessary. Do not introduce `class`.
+sections where necessary.
+Do not introduce `class`.
### 7.2 Member ordering within a type
@@ -299,6 +298,8 @@ Follow this ordering within a `struct`:
Data members are always in the `private` section and always prefixed with `m_`.
+See the following code snippet for examples:
+
```cpp
struct facet_registry
{
@@ -320,9 +321,10 @@ private:
### 7.3 `explicit` on single-argument constructors
-Mark every single-argument constructor `explicit` unless an implicit conversion is
-intentional and documented. A deliberate implicit constructor must be accompanied by
-a comment explaining the decision.
+Mark every single-argument constructor `explicit` unless an implicit conversion is intentional and documented.
+A deliberate implicit constructor must be accompanied by a comment explaining the decision.
+
+See the following code snippet for examples:
```cpp
// Correct
@@ -337,8 +339,9 @@ constexpr page(chunk other) : chunk{other} {}
### 7.4 Declare deleted special members explicitly
-If a type is not copyable or not movable, declare the deleted special members
-explicitly rather than relying on implicit suppression.
+If a type is not copyable or not movable, declare the deleted special members explicitly rather than relying on implicit suppression.
+
+See the following code snippet for examples:
```cpp
device(device const &) = delete;
@@ -347,16 +350,15 @@ auto operator=(device const &) -> device & = delete;
### 7.5 Virtual destructors
-Every base class with virtual member functions must have a `virtual` destructor,
-defaulted if not otherwise needed.
+Every base class with virtual member functions must have a `virtual` destructor, defaulted if not otherwise needed.
+
+See the following code snippet for examples:
```cpp
virtual ~driver_descriptor() = default;
virtual ~facet_registry_observer() = default;
```
----
-
## 8. Static Singletons
Several subsystems expose a single global instance via an `init()`/`get()` pair.
@@ -365,8 +367,9 @@ Follow this pattern:
- Store the instance in an anonymous namespace as a `constinit std::optional<T>`.
- `init()` asserts the instance is not yet constructed, then `emplace()`s it.
- `get()` asserts the instance exists and returns a reference to it.
-- Both functions panic on violation rather than returning an error code, because
- incorrect call order is a programming error, not a recoverable runtime condition.
+- Both functions panic on violation rather than returning an error code, because incorrect call order is a programming error, not a recoverable runtime condition.
+
+See the following code snippet for examples:
```cpp
namespace
@@ -393,13 +396,12 @@ auto device_registry::get() -> device_registry &
}
```
----
-
## 9. Enumerations
-All enumerations use `enum struct` (scoped enums), never plain `enum`. Specify the
-underlying type explicitly when the representation matters (e.g., for hardware
-register fields).
+All enumerations use `enum struct` (scoped enums), never plain `enum`.
+Specify the underlying type explicitly when the representation matters (e.g., for hardware register fields).
+
+See the following code snippet for examples:
```cpp
// Correct
@@ -414,57 +416,49 @@ enum struct state
enum state { uninitialized, present, bound };
```
----
-
## 10. Naming
-| Symbol | Convention | Example |
-|---|---|---|
-| Types (struct, enum) | `lower_case` | `device_registry`, `facet_id` |
-| Functions and methods | `lower_case` | `add_child`, `make_instance` |
-| Local variables | `lower_case` | `entry`, `block_index` |
-| Private data members | `m_` prefix, `lower_case` | `m_entries`, `m_driver_data` |
-| Template type parameters | `CamelCase` | `ValueType`, `FacetType` |
-| Constants and constexpr variables | `lower_case` | `page_size`, `direct_block_count` |
-| Type aliases | `lower_case` | `value_type`, `size_type` |
-| Namespaces | `lower_case` | `kapi::devices`, `kernel::vfs` |
+| Symbol | Convention | Example |
+|-----------------------------------|---------------------------|-----------------------------------|
+| Types (struct, enum) | `lower_case` | `device_registry`, `facet_id` |
+| Functions and methods | `lower_case` | `add_child`, `make_instance` |
+| Local variables | `lower_case` | `entry`, `block_index` |
+| Private data members | `m_` prefix, `lower_case` | `m_entries`, `m_driver_data` |
+| Template type parameters | `CamelCase` | `ValueType`, `FacetType` |
+| Constants and constexpr variables | `lower_case` | `page_size`, `direct_block_count` |
+| Type aliases | `lower_case` | `value_type`, `size_type` |
+| Namespaces | `lower_case` | `kapi::devices`, `kernel::vfs` |
-Namespaces reflect directory structure: `kernel::vfs`, `kernel::filesystems::ext2`,
-`arch::devices`, etc.
-
----
+Namespaces reflect directory structure: `kernel::vfs`, `kernel::filesystems::ext2`, `arch::devices`, etc.
## 11. `[[nodiscard]]`
-Mark any function `[[nodiscard]]` whose return value the caller should not silently
-discard. This includes in particular:
+Mark any function `[[nodiscard]]` whose return value the caller should not silently discard.
+This includes in particular:
- All functions returning `kstd::result<T>`.
- All query functions (getters, lookups) that return computed data.
- Factory functions and builder utilities.
+See the following code snippet for examples:
+
```cpp
[[nodiscard]] auto resolve(kapi::capabilities::facet_id id, std::string_view name) -> void *;
[[nodiscard]] auto children() const -> kstd::vector<kstd::shared_ptr<device>>;
[[nodiscard]] auto request_resource(resource_type type, std::size_t index = 0) const -> kstd::result<resource>;
```
----
-
## 12. `constexpr` and `constinit`
-Mark functions `constexpr` whenever they can be evaluated at compile time or in
-constant expressions, even if they are also called at runtime. Mark module-scope
-variables `constinit` to guarantee zero-initialization before any dynamic
-initialization runs — important in a kernel with no well-defined global
-initialization order.
-
----
+Mark functions `constexpr` whenever they can be evaluated at compile time or in constant expressions, even if they are also called at runtime.
+Mark module-scope variables `constinit` to guarantee zero-initialization before any dynamic initialization runs.
## 13. Documentation
-Every non-trivial public type, function, and data member must be documented with a
-Doxygen comment using the `//!` line style.
+Every non-trivial public type, function, and data member must be documented with a Doxygen comment using the `//!` line style.
+
+See the following code snippet for examples:
+
```cpp
//! A brief one-line description.
@@ -477,16 +471,15 @@ Doxygen comment using the `//!` line style.
auto add_child(kstd::shared_ptr<device> child) -> void;
```
-Doxygen grouping (`@addtogroup`, `@{`, `@}`) is used to organise the API surface into
-logical sections visible in generated documentation.
-
----
+Doxygen grouping (`@name`, `@{`, `@}`) may be used to organise the API surface into logical sections visible in generated documentation.
## 14. Header Guards
-All headers use traditional include guards, not `#pragma once`. The guard name follows
-the pattern `TEACHOS_<SUBPACKAGE>_<PATH>_HPP`, where each path component is
-uppercased and separators are replaced by `_`.
+All headers use traditional include guards, not `#pragma once`.
+The guard name follows the pattern `TEACHOS_<SUBPACKAGE>_<PATH>_HPP`, where each path component is uppercased and separators are replaced by `_`.
+The only exception to this pattern are the bundled libraries, which follow the pattern `<LIBRARYNAME>_<SUBPACKAGE>_<PATH>_HPP`.
+
+See the following code snippet for examples:
```cpp
#ifndef TEACHOS_KAPI_DEVICES_BUS_HPP