C+
Introduction · View as Markdown

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.