# psi C style guide This document describes the conventions for the C implementation of `psi`. Formatting is enforced by [`.clang-format`](.clang-format) and naming by [`.clang-tidy`](.clang-tidy); everything a tool cannot check is described here and is expected in review. The guiding idea: **idiomatic, data-driven C that reads like the Rust original in naming**, with Allman bracing as the one deliberate departure from typical C style. --- ## 1. Language & build - **C17**, compiled with `-Wall -Wextra -Werror -pedantic`. Warnings are errors; keep every build clean. - Build system is **premake5** (`premake5 gmake && make config=release`). Public headers live in `include/`, implementation in `src/`, the test runner in `tester/`. - Portable across x86_64 and ARM64 (the only supported architectures). Platform-specific code (SIMD intrinsics) is `#ifdef`-gated with a scalar fallback. --- ## 2. Formatting (enforced by clang-format) Run before committing: ```bash find include src tester -name '*.c' -o -name '*.h' | xargs clang-format -i ``` CI check (no diffs allowed): ```bash find include src tester -name '*.c' -o -name '*.h' | xargs clang-format --dry-run -Werror ``` ### Braces — Allman, everywhere Opening brace on its own line for functions, structs, enums, unions, and control blocks. ```c struct PsiComplex { double real; double imaginary; }; double psi_abs_complex(struct PsiComplex z) { return sqrt(psi_norm2_complex(z)); } ``` **Exception (tool limitation):** compound literals keep the brace on the same line as the type, because clang-format cannot break it: ```c return (struct PsiComplex){ a.real + b.real, a.imaginary + b.imaginary, }; ``` ### Single statements omit braces An `if`/`for`/`while` with a single-statement body has no braces; the statement goes on the next line. ```c if (index >= row->width) return; for (size_t i = 0; i < count; i++) sum = psi_add_complex(sum, values[i]); ``` Use braces once a body has more than one statement. ### Indentation — tabs Tabs for indentation (width 4). Alignment (e.g. wrapped arguments under an open paren) uses spaces, so alignment survives any tab width. ### Pointers bind to the type ```c struct PsiComplex* data; void psi_free_vector(struct PsiVector* v); const size_t* targets; ``` Not `struct PsiComplex *data`. ### Line length & wrapping - 100-column limit. Long lines wrap; continuation indents two tabs, wrapped call/parameter lists align under the opening paren. ```c static void execute_kernels(struct PsiVector* state, const struct PsiKernel* kernels, size_t count, size_t num_qubits, struct PsiRuntimeConfig config) { ... } ``` ### Includes Not sorted by the tool — order them yourself. Convention: system headers (`<...>`) first, a blank line, then project headers (`"..."`). `include/psi.h` orders its headers by dependency layer, not alphabetically. --- ## 3. Naming (enforced by clang-tidy) | Kind | Convention | Example | |------|-----------|---------| | Structs | `Psi` + PascalCase | `struct PsiQuantumRegister` | | Enums | `Psi` + PascalCase | `enum PsiGateType` | | Enum constants | `UPPER_CASE` | `PSI_GATE_H`, `PSI_SIMD_NEON` | | Functions | `snake_case` | `psi_add_complex`, `apply_kernel` | | Parameters / variables | `snake_case` | `num_qubits`, `target_bit` | | Macros / compile-time consts | `THIS_CASE` | `PSI_VERSION_MAJOR`, `INV_SQRT_2` | | Function-like macros | `psi_` + `snake_case` | `psi_matrix`, `psi_column_vector` | ### Library prefix Every **public** symbol is namespaced: - functions → `psi_...` - structs → `Psi...` - macros / constants → `PSI_...` File-local `static` helpers are **not** prefixed (`apply_pair`, `build_pairs`, `op_to_kernel`). ### Data-driven, action-first (not OOP) The operation leads and the data is an argument — think free functions over data, not methods bound to a type. `psi__(...)`: ```c psi_add_complex(a, b) /* not psi_complex_add */ psi_new_quantum_gate(...) /* not gate_new(...) */ psi_apply_gate(®, ...) /* verb first */ ``` *Not enforceable by tooling — maintained in review.* --- ## 4. Types - **`double` only** for floating point. No `float`, no fixed-width float aliases (their sizes are not guaranteed across platforms). - **Fixed-width ``** types (`uint32_t`, `int64_t`, …) over `int`/`unsigned`/`long`. - **`size_t`** for sizes, counts, and indices (the Rust `usize`). - **No `typedef` on structs, enums, or unions** — always spell `struct PsiComplex`, `enum PsiGateType`. --- ## 5. Memory & ownership There is no garbage collector; ownership is explicit and follows a few rules. - Every type that owns a heap allocation has a matching `psi_new_*` / `psi_free_*` pair, and `psi_free_*` takes a pointer and nulls the freed fields. ```c struct PsiVector v = psi_new_vector(4, PSI_COLUMN_VECTOR); ... psi_free_vector(&v); ``` - **The caller frees.** Functions that allocate and return a value (`psi_add_vector`, `psi_clone_matrix`, the gate factories, the renderers' returned strings) transfer ownership to the caller. - **Read-only args are passed by value**, which shares the underlying buffer and never frees it. Mutators and destructors take a pointer. - **Constructors that take ownership** consume what they are handed (e.g. a gate takes ownership of its matrix; `psi_apply_custom` takes ownership of the custom gate). Builders that take an existing value they should not consume make a copy (`psi_new_vector_from`, `psi_new_quantum_register_from`). - **Allocation idiom:** `sizeof *ptr`, not the repeated type. ```c struct PsiKernel* out = malloc(count * sizeof *out); ``` - Verify with sanitizers during development: ```bash clang -std=c17 -Iinclude -Isrc -Itester -fsanitize=address,undefined -g \ src/psi.c src/**/*.c tester/*.c -o /tmp/psi -lm && /tmp/psi ``` --- ## 6. Error handling - Preconditions and programmer errors (dimension mismatches, out-of-range indices) use `assert`. The Rust original returned `Option`/panicked; in C these are `assert`s, since they indicate caller bugs rather than recoverable conditions. - `malloc`/`calloc`/`realloc` results are `assert`ed non-NULL (with `|| size == 0` where a zero-size allocation is legal). --- ## 7. Comments - **Avoid narration.** Code should read on its own; do not annotate ported steps or restate what a line does. - **Short math-formula comments are welcome** where they clarify an expression: ```c struct PsiComplex psi_mul_complex(struct PsiComplex a, struct PsiComplex b) { // (a + bi)(c + di) = (ac - bd) + (ad + bc)i return (struct PsiComplex){ a.real * b.real - a.imaginary * b.imaginary, a.real * b.imaginary + a.imaginary * b.real, }; } ``` *Not enforceable by tooling — maintained in review.* --- ## 8. Project layout ``` include/ public API (mirrors src/ subtree) psi.h umbrella header — includes the whole public API maths/ complex, vector, matrix, format, simd core/ quantum_components, gates, custom_gate, classical_components, circuit, kernel, runtime, noise visualizer/ renderer src/ implementation, same subtree; may also hold private headers tester/ assertion-based test suite ``` - Headers are `#pragma once` and included **root-relative**: `#include "maths/complex.h"`. - `include/psi.h` is the umbrella: `#include ` pulls in the entire public API. Add each new module's public header to it. - Private, implementation-only headers live under `src/` (e.g. `src/visualizer/grid.h`). --- ## 9. Testing - `tester/` is an assertion suite with subcommands (`clifford`, `non-clifford`, `custom`, `kernels`, `simd`, `noise`, `all`, `help`). - Tests assert against known states (Bell, GHZ, rotations, fusion, noise purity, …) and check that all runtimes agree. The process exit code reflects pass/fail (CI-friendly). ```bash premake5 gmake && make config=release ./bin/release-/tester # all suites ./bin/release-/tester noise # one suite ``` --- ## Tooling summary | Concern | Tool | Command | |---------|------|---------| | Formatting | clang-format | `... | xargs clang-format --dry-run -Werror` | | Naming | clang-tidy | `clang-tidy -- -std=c17 -Iinclude -Isrc` | | Build | premake5 + make | `premake5 gmake && make config=release` | | Tests | tester | `./bin/release-/tester` | | Leaks / UB | sanitizers | build with `-fsanitize=address,undefined` |