Design FAQ
C+ is explicit by design. Most of its surprising answers are the same answer worn differently: one way to write a thing, checked by the compiler, with nothing left implicit. The rules below are not style guidance — they are compiler-enforced, and the error you hit when you break one is named. This page explains the why. For the normative what, see the language specification.
Why is the language frozen?
C+ reached the surface it set out to have and stopped. Freezing at v0.0.22 turns "the language" into a fixed target: tools, the code graph, the error codes, and anything written against the grammar no longer chase a moving syntax. New capability did not stop — it moved to where it belongs, in packages and tooling. See Stability and versioning for exactly what the freeze guarantees.
Why is there no null?
Because a pointer that might be nothing, with no type to say so, is the
most reliable source of crashes there is. C+ expresses "maybe absent" in
the type, with Option[T], so the compiler makes you handle the empty
case:
fn find(k: str) -> Option[i32] {
if k == "answer" { return Option[i32]::Some(42); }
return Option[i32]::None;
}
There is no null keyword and no null value in safe code. In FFI, a null
pointer is written 0 as *T inside an unsafe block — visibly unsafe,
never implicit. See Ownership and
Error handling.
Why no closures or lambdas?
Because a closure hides two things — captured state and a heap allocation
— behind a value that looks free. C+ keeps both visible. Callbacks are a
named fn plus an explicit user_data pointer, which is exactly the
shape a C API already expects:
fn on_tick(ud: *u8, n: i32) { /* ... */ }
extern fn lib_subscribe(cb: fn(*u8, i32), ud: *u8);
A function pointer is the address of a top-level function — no environment capture, no allocation, no surprise.
Why no &T / &mut T references?
Because borrowing in C+ is a property of a parameter, not a type you pass
around. Methods take an explicit receiver — self, mut self, or
move self — and ordinary by-value passing covers the rest. Removing
value-site references removes a whole category of lifetime puzzles while
the borrow checker still proves there are no
conflicting accesses. Unary & / &mut parse but are rejected by the
type checker.
Why no exceptions, try, or ??
Because an exception is a control-flow path the types do not mention. C+
makes failure an ordinary value: a tagged enum you return and the caller
must handle, by match or guard let.
enum Parse { Ok(i32), Bad, Overflow }
return match parse(s) {
Parse::Ok(v) => v,
Parse::Bad => 0 -% 1,
Parse::Overflow => 0 -% 2,
};
Every path that can fail says so in its return type, and exhaustiveness is checked. See Error handling and Pattern matching.
Why must every conversion be an as cast?
Because an implicit widening or sign change is a decision the compiler
made on your behalf, and those are the decisions that bite. C+ has no
implicit numeric or pointer conversions: every width or representation
change is a written as. A mismatch without one is a type error, not a
silent coercion. See Primitives and
Operators.
Why no overloading, and why [T] instead of <T>?
One name maps to one signature, so reading a call tells you which function
runs without resolving an overload set. Generics use square brackets —
vec::new::[i32](), Option[i32] — because < and > are also
comparison operators, and a < b > (c) has no single reading. Brackets
keep both the grammar and the reader unambiguous. See
Functions for generics.
Why explicit return?
A trailing expression is the value of a block, but a function returns
its value through an explicit return EXPR;. The function boundary is
where it matters most that "this is the result" is stated, not inferred
from the absence of a semicolon.
Why does so little live in the core language?
This is the point of the freeze. A small, fixed core that a person — or a
model — can hold in its head is worth more than a large one that keeps
growing. So capability lives in packages: UI builders, FFI bindings,
numeric kernels, embedded peripherals are all ordinary code under
vendor/, each versioned on its own cadence. The
builder-block DSL is the clearest example — the
compiler owns a tiny, general construction syntax, and packages supply the
meaning. See Modules and packages and the
packages index.
Is a safe language a slow one?
No, and that is a design constraint, not a hope. There is no garbage collector and no hidden runtime: memory safety is established statically by the borrow checker, generics are monomorphized to concrete code, and the output is a standard object file over the C ABI. The checks that remain at runtime are the ones you would write anyway, such as array bounds. Safety is paid for at compile time, not on every call.
Why does the design keep mentioning agents and LLMs?
Because code is increasingly written by models, and the same properties
that make C+ explicit for a person make it tractable for a model: one way
to express a thing, no hidden control flow, a type that names every
outcome, and a resolved, typed code graph the
toolchain exposes through cpc query and cpc mcp. A language a model
can reason about without guessing is a language that produces fewer wrong
programs. The constraints in this FAQ are what make that possible.