# CobaltC

CobaltC is a language that exists mainly as a document: the "CobaltC Programming Language Specification 1.0.3", published as an appendix (the sixth, going by its URL) to *The Wrong Memory*, a book of essays hosted at strawberry9.github.io [[cobaltc-successor-to-c]]. The clipped text names no author. The only identity it gives is the `strawberry9` GitHub account, which hosts the book and a "CobaltC Semantic Compiler".

The document is careful to say what it is, and it says two different things. A note at the top calls CobaltC a thought experiment, a speculative systems language whose job within the book is to show how a memory-safe C successor ought to define its semantic boundaries, and not a drop-in replacement for an ISO-level standard. The body reads as a standard anyway. It uses RFC-style MUST and SHOULD, declares CobaltC 1.0 a frozen language edition with 1.0.3 as a corrected publication, marks itself "Status: Normative", and defines conformance levels and a conformance test methodology. The reference implementation it links covers semantic analysis only: lexing, parsing, name resolution, type checking, initialization, and ownership and borrowing analysis, emitting what it calls an Explainable Semantic Intermediate Representation (ESIR), which records the program's resolved semantics along with the rules used to establish them. Nothing in the text describes code generation. The clip is about 9,000 lines: 87 numbered sections and appendices A through H.

## The proposition

The book's argument, as the preface restates it, is that bolting a safe subset onto an unsafe language usually fails, because the core semantics still permit the invariant violations. CobaltC puts ownership and borrowing into the core type system instead. The preface credits Rust with showing that these ideas, most of which it did not originate, combine into a practical high-performance systems language, and says CobaltC adopts that safety architecture without Rust's syntax or overall philosophy.

The opening example is two pointers into a 2×2 array. In C the programmer knows they point at different cells and has to keep it that way; the spec's slogan for C is "You have the power. Don't screw it up." In CobaltC the same code takes two `&mut` borrows, and the compiler establishes that the storage is distinct, the accesses are disjoint, and `grid` still owns the values: "You have the power. Let's prove you're exercising it correctly."

```text
mut i32[2][2] grid = [[1,2],[3,4]];
mut i32* a = &mut grid[0][0];
mut i32* b = &mut grid[1][1];
*a = 7;
*b = 8;
```

## What it looks like

Declarations stay type-first, as in C, and `*` is kept but changes meaning. A plain pointer type is now a borrow, called a managed pointer, and never an owner:

```text
String*        shared, read-only, non-null
String*?       shared, nullable
mut String*    exclusive, mutable, non-null
mut String*?   exclusive, mutable, nullable
raw String*    unmanaged address; dereference requires unsafe
```

`&value` takes a shared borrow and `&mut value` a mutable one. Non-copyable values move only with an explicit `move`. There is no `let` and no lifetime annotation anywhere in the grammar: lifetimes are always inferred, and a borrow may end at its last use before the end of its scope. Generics in 1.0 are deliberately unconstrained, with no user-facing constraint syntax, which the spec calls an edition boundary rather than an omission.

There are no classes, inheritance or dynamic dispatch. Functions can be associated with a type as `Type::name`, but association only affects naming and lookup, so there is no implicit receiver: `Stack<i32>::push(&mut stack, 10)` and the value-qualified `stack::push(&mut stack, 10)` call the same function with the same explicit arguments, and `.` never performs a method call. Visibility exists only at module level. A declaration is accessible from outside only if the module lists it in an `export { ... }` block, and exporting a type exports its name, not its representation, so exported types are opaque by default.

A few choices depart from both C and Rust.

- Integer overflow, division by zero and out-of-range shifts are arithmetic failures, never wrapping. A compile-time error when provable, a runtime failure otherwise. The reporting mechanism (trap, checked error, termination) is left to each implementation to choose and document, but a failed operation must never produce a value. Overflow can't silently produce an undersized allocation, which the spec lists among its core safety properties.
- Slice ranges are inclusive at both ends. `values[0..3]` has four elements, `start > end` is a bounds error instead of an empty slice, and `values[...]` selects everything. Rust and Golang both use half-open ranges.
- `defer` exists alongside destructors. Deferred blocks run in reverse order before the scope's owned locals are destroyed, and a deferred block refers to a binding path rather than capturing a value: reassigning `value` after registering `defer { print(value); }` prints the new value, and moving out of `value` while the defer is pending is a compile error.
- A type can define a destruction hook, `fn File::destroy(mut File* file)`, which releases resources that owned fields don't represent, such as a raw OS handle. The compiler then destroys the owned fields itself. The hook runs exactly once and can't be called explicitly. This is [[raii]], with the split between user cleanup and field destruction written into the rules.
- Match must be exhaustive, recoverable errors are `Result<T,E>` values propagated with postfix `?`, and panics or exceptions don't exist as source-language mechanisms. Abort terminates without guaranteed destruction.
- Identifiers are pinned to Unicode 17.0.0 `XID_Start`/`XID_Continue` plus underscore, must pass the UTS #39 Moderately Restrictive profile, are never normalized or case-folded, and may not contain bidirectional-control or zero-width characters. A later Unicode version can't change what counts as a CobaltC 1.0.3 identifier.

## Ownership, borrowing and lifetimes

The rules follow Rust closely. Each owned value has exactly one owner responsible for destroying it. An aggregate is copyable only if all its owned components are copyable and it defines no destruction hook. Partial moves are tracked: after `move person.name`, `person.address` is still usable, `person` can't be moved whole, and destruction skips the moved field. The borrowing rule is stated as "zero or more compatible shared borrows OR one mutable borrow", disjoint fields can be borrowed mutably at the same time, and an operation that might relocate storage is rejected while an element borrow is live:

```text
mut Vector<String> values = ["hello"];
String* pointer = &values[0];
values::push(&mut values, "world"); // ERROR: active element borrow
print(*pointer);
```

For dynamic indexes the compiler may treat accesses as overlapping, and must not assume two runtime indexes differ.

Shared ownership is not a state of the language. Libraries may implement it, but explicitly. Appendix C proposes something else for graphs, registries and caches: separate identity from ownership. A container owns the objects, other code holds an ordinary identity value such as an index plus a generation counter, and access means resolving that identity into a normal, checked borrow. Its summary is "Ownership → determines lifetime; Identity → identifies an object; Borrow → provides access". [[arena-allocation]] describes the same index-as-handle pattern from the allocator side.

Section 1 lists the properties a conforming compiler must enforce in safe code: moved values can't be used through the old owner, owned storage can't be accessed after its lifetime, no value has two owners or is destroyed twice, conflicting mutable aliases and mutable-plus-shared borrows can't coexist, relocation can't invalidate a live borrow, borrows and slices can't outlive their storage or escape their referent's lifetime, null can't be dereferenced through a non-nullable pointer, safe indexing stays in bounds, and overflow can't produce an undersized allocation.

## Concurrency and the memory model

Safe code must not contain data races, but the core language defines no thread, channel or atomic API; that belongs to the runtime or standard library. What the spec does define, in section 66 and Appendix E, is an ordering model. Evaluations inside one execution context are *sequenced-before* each other. Certain operations *synchronize-with* one another across contexts: a mutex unlock with the next successful lock, thread start with the thread's first evaluation, thread completion with a successful join. *Happens-before* is the transitive closure of the two, and a data race is two conflicting non-atomic accesses that happens-before doesn't order.

The distinction the spec keeps returning to is that ownership decides which context has the authority to access a value, synchronization decides ordering and visibility, and neither implies the other. Moving a value to a thread transfers authority without ordering prior writes unless the transfer operation itself synchronizes. Locking a mutex doesn't make a dangling borrow valid, and joining a thread doesn't revive an object destroyed before the join. Freedom from deadlock, livelock and starvation is explicitly not promised. The spec also says happens-before must not be read as requiring a machine-level memory fence for each relationship, and leaves each atomic ordering mode to document its own guarantees. [[shared-memory-consistency-causality]] works through what those atomic guarantees have to be on real hardware. Rust answers the question of which values may cross a thread boundary with the `Send` and `Sync` traits ([[rust-send-sync]]). CobaltC leaves the capabilities permitted across a thread boundary implementation-defined, which is the largest gap between its guarantees and a buildable language.

## Unsafe code and foreign functions

`unsafe` blocks and `unsafe fn` work as in Rust, and the spec spends several sections insisting on what they don't do: entering an unsafe context doesn't transfer ownership, extend lifetimes, initialize storage, suppress diagnostics, or change the meaning of surrounding safe code. Its rule for safe wrappers is "Unsafe code MAY implement a safe abstraction, but it MUST NOT export an invariant that the safe interface cannot guarantee." That places CobaltC on Rust's side of the argument in [[memory-safety-absolutists]]: `unsafe` is an explicit, local boundary, and safety is a property of interfaces rather than of the absence of unsafe code.

The foreign-function chapters separate two contracts. The ABI, with the platform C ABI as the baseline, decides how values cross the boundary; a semantic contract decides who owns them, how long they stay valid, and who destroys them. The spec's line is "The ABI determines how values cross the boundary; the FFI contract determines what those values mean." Every ownership-sensitive foreign call has to fall under one of five contracts: borrowed, caller retains, caller transfers, callee transfers, or foreign-owned. A call that consumes ownership must also say what happens on failure: ownership transfers only on success, transfers when the call begins, or stays with the caller regardless. Managed pointers must not be passed where a C pointer is expected, and a CobaltC `String` is not a C string.

## The formal layer and conformance

Appendix D gives a sequential semantics with an abstract state Σ = (ρ, μ, Λ, Δ): bindings, store, lifetimes, and pending deferred obligations. Borrows are capabilities (`Shared`, `Exclusive`, `Suspended` during a reborrow), and lifetimes are containment constraints such as `λborrow ⊆ λreferent`. Its example of a function returning one of two borrowed arguments, depending on a condition, constrains the result by both inputs, because the compiler may not pick a lifetime from the branch that happens to run. Appendix E extends the model to concurrency, F to foreign boundaries, G to conformance testing, and H states how the subsystems compose, which amounts to "subsystem composition preserves existing guarantees unless a more specific normative rule explicitly defines otherwise".

Conformance is cumulative across three levels. Core is the language, Standard adds a required library (`Result`, `String`, `Vector`, `Slice`, `Mutex` where concurrency exists, and standard I/O), and Platform adds a declared runtime and ABI profile. Tests are compile-pass, compile-fail, run-time, diagnostic and ABI, and a diagnostic test checks the semantic condition, never the wording. Section 84 states a safety theorem, that a conforming implementation running a conforming safe program must not violate the ownership, initialization, borrowing, lifetime, nullability, bounds or synchronization requirements. Section 86 then concedes that the specification is not a formal, machine-checked proof.

## Reading it critically

Much of the length is restatement. Sections 67 through 83 each end with a "Conformance Requirements" list and a "Summary" closing on a one-line "fundamental rule" in a block quote, and core rules recur three or four times (the borrowing rule appears in sections 41, 43, 48 and Appendix D). Meanwhile the details a compiler writer would need first are delegated as implementation-defined: which capabilities may cross a thread boundary, what atomic ordering modes exist, the signature of `main`, the constant-expression rules, and how arithmetic failure is reported.

The text also disagrees with itself in small places. Section 42, "Shared Borrows", is in the table of contents and missing from the body, which jumps from 41 to 43. Section 31 writes `if condition` without parentheses and section 55 writes `if (value != null)`. The illustrative program in section 87 imports `io` where section 8 uses `std.io`. And the safe-wrapper example in 67.13 indexes a raw buffer under a comment saying "The wrapper establishes that index is in bounds", without any bounds check, in the section whose point is that a wrapper must establish its invariants.

None of this conflicts with the preface's framing. Read as a thought experiment, CobaltC is a careful inventory of the invariants a safe C successor has to own and of where the boundaries fall: ownership against synchronization, ABI against semantic contract, identity against ownership, safe interface against unsafe implementation. As a language someone could implement from this document, it is incomplete. Other C successors in the vault take different routes: [[zig]] and [[c3-lang]] keep manual memory management and rethink C's defaults, while [[simplified-model-of-fil-c]] makes existing C memory-safe at runtime with a garbage collector and pointer capabilities.
