1. Introduction
CobaltC is a statically typed systems programming language providing:
- explicit ownership;
- deterministic destruction;
- compiler-checked borrowing;
- inferred lifetimes;
- explicit nullability;
- bounds-safe operations;
- structured error handling;
- safe concurrency;
- explicit unsafe operations;
- explicit foreign-function interfaces.
The language is intended for software requiring predictable resource management, strong memory safety, native execution and controlled interaction with low-level facilities.
CobaltC does not require tracing garbage collection.
2. Normative Terminology
The words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are normative.
Implementation-defined means that an implementation chooses the behavior and documents that choice.
Undefined behavior is behavior for which this specification imposes no requirements. Safe CobaltC operations MUST NOT introduce undefined behavior merely through ordinary use.
3. Source Files
A CobaltC program consists of one or more source modules.
Source text is Unicode.
Identifiers are case-sensitive.
Whitespace separates lexical tokens where necessary and otherwise has no semantic meaning.
4. Comments
CobaltC supports line comments:
// comment
and block comments:
/*
comment
*/
Comments have no semantic effect.
5. Keywords
The following are reserved:
as
break
case
const
continue
defer
else
enum
extern
false
fn
for
if
import
in
interface
loop
match
move
mut
null
return
static
struct
true
type
unsafe
while
let is not a CobaltC 1.0 keyword.
6. Identifiers
An identifier begins with a Unicode identifier-start character and may contain subsequent identifier characters and digits.
Identifiers are case-sensitive.
The following therefore represent distinct names:
value
Value
VALUE
7. Literals
CobaltC provides:
- integer literals;
- floating-point literals;
- character literals;
- string literals;
- Boolean literals;
- null.
Numeric literals MAY use separators where supported by the implementation, provided separators do not alter their value.
8. Modules
A module declaration has the form:
module example;
A module establishes a namespace.
Modules MAY import declarations from other modules:
import io;
Name resolution is lexical and module-aware.
An unresolved name is a compile-time error.
9. Declarations
CobaltC provides:
const
static
type
struct
enum
interface
fn
Declarations are introduced into their applicable lexical or module namespace.
Inner declarations MAY shadow outer declarations where permitted.
10. Variables
A variable is declared using:
i32 count = 0;
A mutable variable is declared:
mut i32 count = 0;
An uninitialized declaration is permitted:
i32 result;
but result MUST be initialized before it is read.
11. Constants
Constants use:
const i32 maximum = 100;
A constant initializer MUST satisfy the implementation's constant-expression requirements.
A constant cannot be mutated.
12. Primitive Types
CobaltC defines:
bool
char
i8
i16
i32
i64
i128
u8
u16
u32
u64
u128
isize
usize
f32
f64
The fixed-width integer types have exactly their specified widths.
isize and usize are pointer-sized integer
types.
13. Compound Types
CobaltC supports:
- structs
- enums
- tuples
- arrays
- function types
- managed references
- raw pointers
- generic types
- interface-constrained types
Structs and enums are nominal types.
Type aliases do not create new nominal types.
14. Managed References
The notation:
T*
represents a managed non-null reference.
The notation:
T*?
represents a nullable managed reference.
Managed references participate in ownership, borrowing and lifetime checking.
15. Raw Pointers
Raw pointers are represented:
raw T*
Raw pointers are outside the ordinary managed ownership and lifetime guarantees.
Raw-pointer dereference and unrestricted pointer manipulation require an unsafe context.
16. Mutability
A mutable binding permits mutation through that binding where no ownership or borrowing rule prohibits the operation.
Mutability does not override aliasing rules.
For example, having a mutable owner does not permit mutation while an incompatible borrow remains active.
17. Type Compatibility
Assignments, function arguments and return values MUST have compatible types.
Implicit conversions MUST NOT silently:
- remove nullability;
- create ownership;
- destroy ownership;
- violate mutability;
- invalidate a lifetime guarantee;
- perform unsafe reinterpretation.
Explicit conversion facilities MAY be provided.
18. Type Inference
CobaltC permits inference where the language grammar and context establish a unique type.
Inference MUST preserve all semantic distinctions relevant to:
- ownership;
- mutability;
- nullability;
- borrowing;
- lifetime.
Inference MUST NOT make an unsafe operation appear safe.
19. Generic Types
Generic types and functions are statically checked.
Example:
fn identity<T>(T value) -> T
{
return value;
}
Generic constraints MUST be satisfied before a generic entity is used.
20. Interfaces
Interfaces define required operations.
Example:
interface Printable
{
fn print();
}
A generic constraint may require an implementation:
T: Printable
The compiler MUST verify that required interface operations exist.
21. Structs
A struct defines a nominal aggregate:
struct Point
{
i32 x;
i32 y;
}
Struct fields have declared types.
Owned fields participate in the enclosing value's ownership and destruction semantics.
22. Enums
An enum defines a finite set of variants:
enum Status
{
Ready,
Running,
Failed
}
Variants MAY contain associated values:
enum Result<T,E>
{
Ok(T),
Err(E)
}
23. Tuples
Tuples combine a fixed number of values.
Tuple elements are independently typed.
Tuple ownership follows the ownership rules of their elements.
24. Arrays
Arrays contain a fixed number of elements:
T[N]
The length is part of the array type.
Safe indexing MUST remain within the valid range.
25. Functions
A function is declared:
fn add(i32 a, i32 b) -> i32
{
return a + b;
}
The number and types of arguments MUST match the function signature.
Ownership and borrowing requirements apply to arguments and return values.
26. Expressions
Expressions produce values or perform operations.
The core expression categories include:
- names;
- literals;
- calls;
- construction;
- member access;
- indexing;
- borrowing;
- unary operators;
- binary operators;
- assignment.
27. Operator Precedence
From highest to lowest:
| Level | Operators |
|---|---|
| 1 | call, indexing, member access |
| 2 | !, unary +, unary -, move, borrow |
| 3 | *, /, % |
| 4 | +, - |
| 5 | <<, >> |
| 6 | <, <=, >, >= |
| 7 | ==, != |
| 8 | & |
| 9 | ^ |
| 10 | | |
| 11 | && |
| 12 | || |
| 13 | assignment |
Binary operators are left-associative unless otherwise specified.
Assignment is right-associative.
28. Arithmetic
Integer and floating-point operations follow the semantics of their respective types.
An operation that cannot safely produce the required result MUST follow the type's specified overflow or failure semantics.
Safe arithmetic MUST NOT silently produce memory corruption.
29. Equality
Equality requires compatible operands.
Value equality compares values according to the type's equality semantics.
Where pointer identity is explicitly requested, pointer equality compares identity rather than recursively comparing referents.
30. Assignment
Assignment requires a valid mutable destination.
Compound assignment follows the corresponding arithmetic or bitwise operation.
Assignment does not implicitly transfer ownership unless the operation constitutes a move.
31. Function Calls
A call is valid only if:
- the function is resolvable;
- the argument count is correct;
- arguments have compatible types;
- ownership transfers are valid;
- borrows remain valid;
- generic constraints are satisfied.
32. Conditional Execution
CobaltC provides:
if condition
{
...
}
else
{
...
}
The condition MUST satisfy the Boolean condition requirements.
33. Loops
CobaltC provides:
while
for
loop
break exits the applicable loop.
continue begins the next iteration.
34. Match
Pattern matching is provided by match:
match value
{
Some(x) => use(x),
None => use_default()
}
A match over an exhaustively known variant set MUST handle every possible case.
The compiler MUST reject statically non-exhaustive matches.
35. Return
return transfers control from the current function.
Returning an owned value transfers ownership to the caller.
Returning a reference is permitted only if its lifetime remains valid after the function returns.
A reference to an ordinary local variable MUST NOT be returned.
36. Defer
defer schedules work for scope exit:
{
defer { close_resource(); }
use_resource();
}
Deferred blocks execute in reverse registration order.
Deferred operations themselves obey ordinary ownership and lifetime rules.
37. Definite Initialization
A value MUST be initialized before it is read.
The compiler MUST perform control-flow-sensitive definite-initialization analysis.
This is invalid:
i32 value;
if condition
{
value = 10;
}
print(value);
unless the compiler can prove that every path reaching
print initializes value.
38. Ownership
Ownership is a fundamental part of CobaltC's type and runtime model.
An owned value has one responsible owner unless its type explicitly implements shared ownership.
The owner is responsible for eventual destruction.
39. Move Semantics
A move transfers ownership.
File a = open("data.txt")?;
File b = move a;
After the move, a MUST NOT be used as an owner of the
transferred value.
A moved-from binding MAY remain in scope, but its moved value is unavailable except as permitted by explicitly defined partial-move rules.
40. Copy Semantics
A type may support copying.
Implicit copying is permitted only when the type's semantics explicitly permit it.
Copying produces an independent value according to the type's copy contract.
Copying is not ownership transfer.
41. Partial Moves
For aggregate values, an individual owned component MAY be moved independently when the compiler can track the resulting state.
A moved component cannot subsequently be used through its original ownership path.
Unaffected independent components MAY remain usable.
42. Borrowing
A borrow provides access to an owned value without transferring ownership. Borrowing does not create a new owner of the borrowed value.
CobaltC supports shared borrows and mutable borrows. A shared borrow provides read access to its referent. A mutable borrow provides exclusive mutable access to its referent.
The reference type T* represents a managed, non-null reference. The expression &expr creates a shared borrow, and the expression &mut expr creates a mutable borrow when the applicable ownership and mutability rules permit the operation.
The fundamental borrowing rule is:
zero or more compatible shared borrows OR one mutable borrow A shared borrow is compatible with another shared borrow when neither borrow provides conflicting access to the same storage. A mutable borrow is incompatible with any other borrow of the same storage that would permit conflicting access.
Conflicting borrows MUST be rejected.
String value = "hello"; String* reference = &value; print(*reference); print(value); The borrow does not transfer ownership of value. The binding value remains the owner of the string.
A value MUST NOT be moved while a live borrow of that value would be invalidated by the move.
String value = "hello"; String* reference = &value; String moved = move value; print(*reference); // ERROR: `value` was moved while borrowed A borrow MUST NOT outlive its referent. A reference MUST NOT be used after the storage required by that reference has ceased to be valid.
43. Shared Borrows
A shared borrow provides read access to its referent. Multiple compatible shared borrows MAY exist simultaneously.
String value = "hello"; String* first = &value; String* second = &value; print(*first); print(*second); Shared borrows MAY alias the same value when they provide only compatible shared access.
String value = "hello"; String* first = &value; String* second = &value; String* third = &value; print(*first); print(*second); print(*third); A shared borrow MUST NOT be used to perform mutable access to its referent.
String value = "hello"; String* reference = &value; append(*reference, "!"); // ERROR: shared borrow does not permit mutation A mutable borrow MUST NOT be created while an incompatible shared borrow remains live.
mut String value = "hello"; String* shared = &value; String* mutable = &mut value; // ERROR: `value` is already borrowed print(*shared); append(*mutable, "!"); The implementation MUST permit multiple compatible shared borrows and MUST reject conflicting mutable access.
44. Mutable Borrows
A mutable borrow provides exclusive mutable access to its referent.
A mutable borrow requires a mutable owner or otherwise mutable storage as defined by the applicable type rules.
mut String value = "hello"; String* reference = &mut value; append(*reference, " world"); print(*reference); While a mutable borrow is live, another mutable borrow of overlapping storage MUST NOT be created.
mut String value = "hello"; String* first = &mut value; String* second = &mut value; // ERROR: conflicting mutable borrow append(*first, "!"); append(*second, "?"); A mutable borrow MUST NOT coexist with a conflicting shared borrow.
mut String value = "hello"; String* shared = &value; String* mutable = &mut value; // ERROR: conflicting borrow print(*shared); append(*mutable, "!"); A mutable reference MAY subsequently be used for shared access when no conflicting mutable use of that reference remains live.
mut String value = "hello"; String* mutable = &mut value; append(*mutable, "!"); String* shared = mutable; print(*shared); Mutable access MUST remain exclusive for the duration of the applicable mutable borrow.
45. Borrow Lifetime
A borrow has a lifetime during which its reference remains valid and the associated borrowing restrictions apply.
A borrow lifetime MUST NOT exceed the lifetime of its referent.
String* reference; { String value = "hello"; reference = &value; print(*reference); } print(*reference); // ERROR: `value` no longer exists A borrow is live at a program point when a reference derived from that borrow may subsequently be used and the borrow is therefore required to remain valid at that point.
A borrow MAY cease to be live before the end of its enclosing lexical scope when no subsequent use of a reference derived from that borrow requires the borrow to remain live.
mut String value = "hello"; { String* reference = &value; print(*reference); } String* mutable = &mut value; append(*mutable, " world"); The preceding program is valid because the shared borrow is no longer live when the mutable borrow is created.
The compiler MUST reject a program in which a reference could be used after the lifetime of its referent has ended.
A borrow MUST remain valid across every operation in which the corresponding reference is used.
46. Function Parameters and Returned Borrows
A function MAY accept a managed reference as a parameter. Passing a reference to a function provides access to the referenced value and does not transfer ownership.
fn length(String* value) -> usize { return length_of(*value); } String value = "hello"; usize size = length(&value); print(value); print(size); A function MAY return a borrowed reference when the returned reference is guaranteed not to outlive its referent.
fn identity(String* value) -> String* { return value; } String value = "hello"; String* result = identity(&value); print(*result); When a returned reference is derived from a borrowed parameter, the returned reference MUST NOT outlive the source borrow.
fn first(String* value) -> String* { return value; } String value = "hello"; { String* result = first(&value); print(*result); } A function MUST NOT return a reference to an ordinary local value whose lifetime ends when the function returns.
fn invalid() -> String* { String value = "hello"; return &value; // ERROR: returned borrow outlives `value` } A function MAY return a reference to storage whose lifetime is independently guaranteed to outlive the returned reference.
CobaltC uses inferred borrow lifetimes. The compiler MUST determine whether parameter borrows, returned borrows, and the referenced storage satisfy the applicable lifetime and borrowing rules.
Ownership MUST NOT be inferred from a borrowed return value. Returning a reference provides access to an existing value; it does not transfer ownership unless an explicitly defined ownership operation is used.
47. Reborrowing
A reference MAY itself be borrowed. Such an operation is a reborrow.
A reborrow creates a new borrow whose access is derived from the existing reference. Reborrowing MUST preserve the ownership, lifetime, and aliasing guarantees of the original borrow.
A mutable reference MAY be temporarily reborrowed as a mutable reference.
fn append_exclamation(String* value) { append(*value, "!"); } mut String value = "hello"; String* reference = &mut value; append_exclamation(&mut *reference); append(*reference, "?"); While a conflicting reborrow is live, the original reference MUST NOT be used in a conflicting manner.
mut String value = "hello"; String* reference = &mut value; String* reborrow = &mut *reference; append(*reference, "!"); // ERROR: `reference` is reborrowed append(*reborrow, "?"); Once the reborrow is no longer live, the original reference MAY be used again, subject to the ordinary borrowing rules.
Reborrowing does not transfer ownership of the underlying value.
48. Field and Partial Borrows
A field of a structure MAY be borrowed independently of another disjoint field.
struct Pair { String first; String second; } mut Pair pair = { first: "one", second: "two" }; String* first = &mut pair.first; String* second = &mut pair.second; append(*first, "!"); append(*second, "?"); A borrow of one field does not, by itself, prevent access to a disjoint field.
mut Pair pair = { first: "one", second: "two" }; String* first = &mut pair.first; append(pair.second, "!"); append(*first, "?"); Two field paths are disjoint when they identify distinct, non-overlapping storage within the same aggregate value.
The compiler MUST permit simultaneous borrows of fields that are established to be disjoint.
The compiler MUST reject simultaneous mutable borrows when the borrowed field paths may refer to overlapping storage.
mut Pair pair = { first: "one", second: "two" }; Pair* whole = &mut pair; String* first = &mut pair.first; use(*whole); // ERROR: conflicting borrow A borrow of an entire aggregate conflicts with a mutable borrow of any overlapping part of that aggregate.
Partial borrowing MUST preserve the same aliasing, lifetime, and exclusivity guarantees as borrowing an entire value.
49. Aliasing
Aliasing occurs when more than one reference provides access to the same underlying storage.
Multiple compatible shared references MAY alias the same storage.
String value = "hello"; String* first = &value; String* second = &value; print(*first); print(*second); A mutable reference is exclusive. A mutable reference MUST NOT coexist with another reference that permits conflicting access to the same storage.
mut String value = "hello"; String* first = &mut value; String* second = &mut value; // ERROR: mutable aliases are prohibited append(*first, "!"); append(*second, "?"); For any storage location accessible through managed references, CobaltC MUST enforce the following invariant:
multiple compatible shared references OR one mutable reference A mutable reference MUST NOT coexist with a conflicting shared reference. Two mutable references MUST NOT provide conflicting access to the same storage.
shared shared shared is therefore permitted when all references provide compatible shared access, while:
mutable mutable and:
shared mutable are prohibited when the references provide conflicting access to the same storage.
These aliasing requirements apply to direct references, reborrows, field borrows, function parameters, returned borrows, and collection element borrows.
Safe CobaltC operations MUST NOT provide a means to bypass these aliasing requirements.
50. Collection Borrowing
Elements of a collection MAY be borrowed when the collection operation and element type permit the corresponding access.
A shared element borrow provides shared access to the element.
Vec<i32> values = [10, 20, 30]; i32* first = &values[0]; i32* second = &values[1]; print(*first); print(*second); A mutable element MAY be borrowed when the collection and element permit mutable access.
mut Vec<i32> values = [10, 20, 30]; i32* first = &mut values[0]; *first = *first + 1; print(values[0]); A collection operation that requires mutable access MUST NOT occur while a conflicting borrow of the collection or of storage within the collection remains live.
mut Vec<i32> values = [10, 20, 30]; i32* first = &values[0]; values.push(40); // ERROR: conflicting borrow of `values` print(*first); If a collection operation may invalidate references to elements, the operation MUST NOT occur while such a reference remains live.
An implementation MUST NOT permit a reference to a collection element to be used after the underlying storage required by that reference has ceased to be valid.
Collection-specific borrowing rules MAY impose additional restrictions where required to preserve ownership, lifetime, aliasing, or storage validity.
51. Borrow Invalidation
A reference is valid only while its referent remains valid and the reference satisfies the applicable borrowing rules.
An operation that conflicts with a live borrow MUST be rejected. A conflicting operation does not by itself make an otherwise valid reference safe to use; the operation is prohibited while the conflicting borrow remains live.
A reference MUST NOT be used after its referent has ceased to exist or after storage required by that reference has ceased to be valid.
String* reference; { String value = "hello"; reference = &value; } print(*reference); // ERROR: referent has been destroyed A value MUST NOT be moved while a live borrow of that value would be invalidated by the move.
String value = "hello"; String* reference = &value; String moved = move value; print(*reference); // ERROR: `value` was moved while borrowed A borrow MAY cease to restrict a value once the corresponding reference is no longer used and the borrow is therefore no longer live.
mut String value = "hello"; { String* reference = &value; print(*reference); } append(value, " world"); print(value); A collection operation that could invalidate an active element borrow MUST be rejected while that borrow remains live.
mut Vec<String> values = ["hello"]; String* reference = &values[0]; values.push("world"); // ERROR: active element borrow print(*reference); The compiler MUST reject a program when it can establish that a reference would be used after its referent becomes invalid, or when an operation would violate the shared-borrow, mutable-borrow, lifetime, move, aliasing, or collection-borrowing rules.
52. Destruction
Owned values are destroyed deterministically.
An ownership responsibility is destroyed exactly once.
Moved-from ownership does not cause a second destruction.
53. Scope Destruction
For ordinary scope exit:
- deferred blocks execute;
- owned locals are destroyed in reverse declaration order;
- control proceeds to the enclosing scope.
An implementation MUST preserve the observable consequences of this ordering.
54. Unwinding
If the implementation supports unwinding, scopes exited by supported unwinding MUST perform their specified destruction.
An implementation may implement panic unwinding using internal exception mechanisms.
55. Abort
An abort terminates execution immediately.
Normal destruction is not guaranteed after an abort.
56. Nullability
Nullable values are explicitly represented by nullable types.
null cannot inhabit a non-nullable type.
Before dereferencing a nullable reference, the compiler MUST establish that it is non-null.
Flow-sensitive refinement is permitted.
57. Bounds Safety
Safe indexing MUST remain within valid bounds.
The compiler MAY eliminate runtime bounds checks when validity has been proven statically.
Unchecked indexing belongs to unsafe facilities.
58. Option<T>
The canonical optional-value type is:
enum Option<T>
{
Some(T),
None
}
Option<T> represents the presence or absence of a
value.
59. Result<T,E>
The canonical recoverable-error type is:
enum Result<T,E>
{
Ok(T),
Err(E)
}
Expected operational failures SHOULD be represented using
Result.
60. Error Propagation
The ? operator propagates a compatible error from the
current operation to the enclosing function.
It is not an exception mechanism.
61. Strings
String owns UTF-8 text storage.
Str represents borrowed UTF-8 text.
A valid text value MUST contain valid UTF-8.
Arbitrary bytes require byte-oriented APIs.
62. Vec<T>
Vec<T> owns dynamically allocated contiguous
storage.
Its capacity MAY exceed its current length.
Operations that change storage in ways that could invalidate active references are governed by the borrowing rules.
63. Slices
A slice provides borrowed access to contiguous storage.
A slice does not own the underlying storage.
A mutable slice provides exclusive mutable access subject to ordinary borrow checking.
64. Box<T>
Box<T> represents unique heap ownership.
Destroying the Box releases its owned allocation and
contained value according to normal destruction rules.
65. Rc<T>
Rc<T> provides reference-counted shared ownership
in contexts where its concurrency restrictions are satisfied.
Reference-counted cycles can prevent destruction.
66. Arc<T>
Arc<T> provides shared ownership suitable for
concurrent transfer when its contained type satisfies the applicable
safety constraints.
Reference counting does not itself provide synchronization for arbitrary interior mutation.
67. Weak<T>
Weak<T> provides non-owning access to
reference-counted objects.
A weak reference does not keep its target alive.
68. Threads
CobaltC supports concurrent execution through threads.
A value transferred to another thread MUST satisfy the required ownership and thread-transfer constraints.
A thread MUST NOT retain an ordinary borrow to a local value that can cease to exist before the borrow is used.
69. Synchronization
Shared mutable state requires synchronization.
The standard synchronization abstractions include:
Mutex
RwLock
Atomic
Channel
Synchronization guards own their applicable lock state and release it on destruction.
70. Mutex
A mutex provides exclusive synchronized access.
A lock guard maintains the ownership of the lock while the guard is live.
Destroying the guard releases the lock.
71. RwLock
A read/write lock permits:
- multiple compatible readers; or
- one writer.
It MUST NOT simultaneously expose incompatible read and write access.
72. Atomics
Atomic operations are indivisible according to the specified atomic type and memory-order semantics.
Atomicity does not itself establish ownership or higher-level synchronization.
73. Channels
Channels provide communication between execution contexts.
Sending a move-only value transfers its ownership according to the channel contract.
The sender MUST NOT subsequently use the moved value as its owner.
74. Data Races
Safe CobaltC code MUST NOT contain an ordinary unsynchronized data race.
The language does not guarantee freedom from logical concurrency errors such as deadlocks or livelocks.
75. Memory Model
The memory model defines the ordering guarantees of synchronization and atomic operations.
Implementations MAY reorder operations internally provided observable behavior remains consistent with the language's memory model.
76. Unsafe Blocks
Unsafe operations require an explicit unsafe context:
unsafe
{
...
}
Unsafe permits operations requiring programmer-supplied invariants.
It does not make an invalid operation intrinsically correct.
77. Raw Memory
Raw-pointer dereference, unchecked memory manipulation and manual allocation/deallocation are unsafe facilities.
An implementation MUST NOT treat arbitrary raw memory as automatically satisfying CobaltC's type, lifetime or ownership requirements.
78. Safe Abstractions over Unsafe Code
Unsafe implementation code MAY be encapsulated by a safe API.
Such an API is valid only if its implementation maintains all invariants promised by its safe interface.
79. Foreign Functions
Foreign functions require explicit declarations.
The baseline foreign ABI is the C ABI.
Foreign functions are not assumed to obey CobaltC ownership, lifetime or safety rules.
80. FFI Ownership
Ownership crossing an FFI boundary MUST be defined by the API contract.
Possible contracts include:
- borrowed for call duration
- caller transfers ownership
- callee transfers ownership
- caller retains ownership
- foreign runtime owns value
The ABI alone does not determine ownership.
81. ABI Profiles
A target ABI profile specifies at minimum:
- architecture
- operating system
- pointer width
- endianness
- alignment
- calling conventions
- C ABI mapping
- atomic capabilities
- runtime model
Binary compatibility is guaranteed only where compatible ABI profiles are used.
82. Runtime
A hosted CobaltC program begins through main.
The runtime provides the facilities required by the language and standard library, including:
- allocation;
- destruction;
- process integration;
- panic handling;
- I/O;
- concurrency;
- platform integration.
The internal runtime architecture is implementation-defined.
83. Allocation
Managed allocation must either produce a valid allocation or produce the specified allocation failure.
An implementation MUST NOT expose an invalid managed object as the result of failed allocation.
84 Panic
A panic represents an unrecoverable program/runtime failure.
An implementation MAY unwind or terminate according to its runtime configuration, provided the selected behavior conforms to the applicable CobaltC rules.
85. Standard I/O
Expected I/O failures are represented using Result-style
APIs.
Typical operations include:
open
read
write
close
Resource-owning I/O objects release their resources deterministically.
86. Standard Concurrency Types
The standard library baseline includes facilities corresponding to:
Thread
Mutex
RwLock
Atomic
Channel
Arc
Their implementations may differ by target but their observable contracts MUST conform.
87. Security and Safety Boundary
CobaltC's safety guarantees apply to conforming safe code.
They do not guarantee:
- algorithmic correctness;
- absence of deadlocks;
- absence of resource exhaustion;
- absence of denial-of-service conditions;
- correctness of unsafe code;
- correctness of foreign code;
- correctness of violated API preconditions.
88. Diagnostics
A conforming compiler MUST reject programs violating normative static rules.
Diagnostic categories include:
syntax error
name-resolution error
type error
initialization error
ownership error
use-after-move
borrow conflict
lifetime violation
nullability violation
bounds violation
non-exhaustive match
generic constraint failure
invalid assignment
Exact diagnostic wording is not normative.
Implementations SHOULD identify relevant source locations and, where practical, explain ownership and lifetime relationships.
89. Implementation-Defined Behavior
Any implementation-defined property MUST be documented.
A compiler cannot claim conformance while silently choosing behavior contrary to a normative requirement.
90. Extensions
An implementation MAY provide extensions.
Extensions MUST be distinguishable from standard CobaltC behavior.
An extension MUST NOT silently change the semantics of a valid CobaltC 1.0.0 program.
91. Conformance Levels
Core Conformance
Requires the language syntax, type system, static semantics, ownership, borrowing, lifetimes and core safety guarantees.
Standard Conformance
Requires Core plus the mandatory standard-library baseline.
Platform Conformance
Requires Standard plus a complete declared runtime and ABI profile for the target.
An implementation claiming conformance MUST state its level.
92. Conformance Testing
A conformance suite MUST contain positive and negative tests covering:
lexing
parsing
name resolution
typing
initialization
ownership
moves
copying
borrowing
lifetimes
destruction
nullability
bounds
patterns
generics
interfaces
Option
Result
collections
strings
concurrency
unsafe boundaries
runtime behavior
FFI
ABI
diagnostics
regressions
A negative test passes when the implementation rejects a program that violates a normative rule.
A positive test passes when the implementation accepts a conforming program and provides behavior consistent with the specification.
93. Compatibility
A CobaltC 1.0.0 program has stable meaning under conforming implementations.
Optimization level MUST NOT change its specified observable semantics.
Binary compatibility is separate from source compatibility and depends on the applicable ABI profile.
94. Versioning
CobaltC 1.0.0 is a closed language edition.
Changes after publication are classified as:
- editorial corrections;
- specification errata;
- future-version language changes.
A semantic change MUST NOT be silently presented as CobaltC 1.0.0 behavior.
95. Final Safety Theorem
The central semantic guarantee of CobaltC is:
A conforming implementation executing conforming safe CobaltC code MUST preserve ownership, initialization, borrowing, lifetime, nullability, bounds and synchronization requirements defined by this specification.
In particular, ordinary safe CobaltC operations cannot be used to create:
- use-before-initialization;
- use-after-move;
- double ownership;
- invalid borrow lifetime;
- conflicting mutable aliasing;
- unchecked nullable dereference;
- unchecked safe out-of-bounds access;
- ordinary unsynchronized data races.
Unsafe and foreign code lie outside these automatic guarantees.
96. Final Reference Model
The complete language model is:
COBALT VALUE
|
+-----------+-----------+
| |
OWNED BORROWED
| |
+-----+-----+ lifetime checked
| |
MOVE COPY
| |
ownership explicit
transfer capability
|
v
deterministic destruction
with the following static safety layers:
TYPE CHECKING
|
DEFINITE INITIALIZATION
|
OWNERSHIP CHECKING
|
BORROW CHECKING
|
LIFETIME CHECKING
|
NULL CHECKING
|
BOUNDS CHECKING
|
CONCURRENCY SAFETY
|
EXPLICIT UNSAFE BOUNDARY
97. Final Status
CobaltC Programming Language Specification 1.0.0
Status: FINAL
The design is frozen. This document is the consolidated normative baseline. Further changes belong either in editorial corrections/errata or in a subsequent language edition.
CobaltC Formal Language Specification: Complete Errata & Additions
This errata provides the exhaustive formal semantics, syntactic grammar rules, and typing judgments required to complete the CobaltC language standard implementation.
30. Lifetime Inference and Region Subtyping Mechanics
Lifetimes track reference validity across control flow boundaries using static region variables. Region subtyping and containment rules determine whether a reference expression is well-formed.
30.1 Region Containment and Outlives Judgments
Rule [Outlives-Transitivity]: Region containment forms a strict partial order over the control flow graph execution tree.
Gamma ? 'a : 'b and Gamma ? 'b : 'c --------------------------------------------- Gamma ? 'a : 'c
Rule [Reference-Subtyping]: Immutable references are covariant over both type parameters and lifetimes.
Sub <: Super and 'a : 'b --------------------------------------------- &'a Sub <: &'b Super
30.2 Lifetime Elision Grammar & Rules
ElisionRule ::= SingleInput ? OutputLifetime = InputLifetime
| ReceiverSelf ? OutputLifetime = SelfLifetime
| Multiple ? CompileError("Explicit lifetime required")
31. Dynamic Indexing and Aliasing Restrictions
Containers supporting runtime dynamic indexing must uphold strict alias-exclusion constraints enforced via path access graphs.
Rule [Disjoint-Access]: A mutable dynamic index access C[i] is legal if and only if its index path does not overlap with any active borrow set.
? v ? ActiveBorrows(G), PathOverlap(v, C[i]) == False
------------------------------------------------------
TypeCheck(C[i]) ? ValidMutableBorrow
32. Generics and Monomorphization Architecture
Generic definitions undergo a two-phase check: preliminary signature validation followed by monomorphized instantiation expansion.
GenericDef ::= fn Ident < GenericParamList > ( ParamList ) TypeConstraintClause
Monomorph ::= Substitute(GenericDef, TypeBindingMap) ? ConcreteAST
33. Error Propagation, Panics, and Unwinding Semantics
Recoverable errors utilize explicit union structures, while systemic faults trigger call-stack unwinding.
Result::= Ok(T) | Err(E) TryOperator ::= Expr ? // Desugars to early-return match on Result::Err(e) PanicRoutine ::= UnwindStack() ? DropResources() ? AbortOrExit()
34. Concurrency, Thread Safety, and Send/Sync Bounds
Cross-thread data safety is statically enforced via marker traits checked during compilation.
Rule [Send-Sync-Bounds]: Type T is Send if all component types implement Send. Type T is Sync iff &'a T is Send.
? f ? Fields(T), Gamma ? f : Send
----------------------------------
Gamma ? T : Send
35. Diagnostics, Source Spans, and Error Recovery
Compilers must track source positions via Span structs and synchronize error recovery at block and statement boundaries.
Span ::= Struct { file_id: u32, start_offset: usize, end_offset: usize }
SyncToken ::= ';' | '}' | 'fn' | 'struct' | 'let'
36. Low-Level Layout, ABI, and Code Generation
Aggregate layouts follow standard C ABI rules, and safe references are lowered into raw pointer targets.
Lowering ::= &'a T ? T* (LLVM Target Pointer)
CallingConv ::= extern "C" { ... } // Standard native platform interop
37. Module System, Path Resolution, and Visibility Rules
Modules map file directory trees to explicit visibility namespaces controlled by pub modifiers.
NOTE: The module system borrows its structural design directly from Rust because that model successfully solves the fragile, global include-file mess found in C and C++.
ModuleDecl ::= mod Ident ; // Resolves to Ident.cc or Ident/mod.cc Path ::= crate:: | self:: | super:: | Ident PathSegments* Visibility ::= pub | pub(crate) | pub(super) | Private (Default)
38. Trait Objects, Object Safety, and Dynamic Dispatch
Dynamic polymorphism is supported via fat pointers combining data pointers and virtual method tables.
Rule [Object-Safety]: A trait is object-safe if all methods exclude generic parameters and enforce Self: Sized bounds where applicable.
? m ? Methods(Trait), GenericParams(m) == Ø ? (IsSized(Self) ? ReceiverIsReference(m))
-------------------------------------------------------------------------------------
IsObjectSafe(Trait) == True
FatPointer ::= Struct { data_ptr: *mut (), vtable_ptr: *const VTable }
VTable ::= Struct { destructor: fn(*mut ()), size: usize, align: usize, methods: [fn(); N] }
39. Heap Allocation, Owned Boxes, and Interior Mutability
Heap ownership is handled via unique smart pointers, while interior mutability wraps dynamic access rules.
Box::= Struct { ptr: *mut T } // Unique heap owner; drops on scope exit Cell ::= Struct { value: T } // Copy-based replacement interior mutability RefCell ::= Struct { value: T, borrow_flag: isize } // Dynamic runtime borrow check
40. Metaprogramming, Macro Expansion, and Hygiene
Declarative macros expand token trees prior to name resolution, maintaining lexical identifier hygiene.
MacroExpand ::= ParseTokenTree(Input) ? MacroRulesPatternMatch ? SubstituteAST Hygiene ::= ScopeID(LocalIdentifier) != ScopeID(MacroDefinitionSite)
A Conformance/Example-Program Example
This example defines a generic Stack<T> backed by
Vec<T>, then demonstrates creating a stack, pushing
values, popping values, and handling an empty-stack error.
module stack_example;
enum Result<T, E>
{
Ok(T),
Err(E)
}
enum StackError
{
Empty
}
struct Stack<T>
{
Vec<T> values;
}
fn Stack_new<T>() -> Stack<T>
{
return Stack<T>
{
values: Vec<T>::new()
};
}
fn Stack_push<T>(mut Stack<T>* stack, T value)
{
stack.values.push(value);
}
fn Stack_pop<T>(mut Stack<T>* stack) -> Result<T, StackError>
{
if stack.values.len() == 0
{
return Err(StackError::Empty);
}
return Ok(stack.values.pop());
}
fn Stack_is_empty<T>(Stack<T>* stack) -> bool
{
return stack.values.len() == 0;
}
fn main() -> i32
{
Stack<i32> stack = Stack_new<i32>();
Stack_push<i32>(&stack, 10);
Stack_push<i32>(&stack, 20);
Stack_push<i32>(&stack, 30);
match Stack_pop<i32>(&stack)
{
Ok(value) =>
{
print(value);
},
Err(StackError::Empty) =>
{
print("stack is empty");
}
}
match Stack_pop<i32>(&stack)
{
Ok(value) =>
{
print(value);
},
Err(StackError::Empty) =>
{
print("stack is empty");
}
}
return 0;
}
Semantics Visible in the Example
The example intentionally exercises several of the normative semantic rules established by the CobaltC 1.0 specification.
-
Generic types:
Stack<T>is a generic nominal type and can be instantiated asStack<i32>. -
Ownership:
Stack<i32> stackowns the stack value. The stack in turn owns its containedVec<i32>. -
Deterministic destruction:
when
stackleaves its scope, its owned contents are destroyed according to CobaltC's deterministic destruction rules. -
Borrowing:
&stackprovides access to the existing stack without transferring ownership toStack_pushorStack_pop. -
Mutable borrowing:
the
mut Stack<T>*parameter permits the called function to modify the borrowed stack while remaining subject to CobaltC's aliasing rules. -
Ownership-preserving access:
Stack_is_emptyaccepts a non-mutating borrow because it only needs to inspect the stack. -
Result-based error handling:
Stack_popreturnsResult<T, StackError>instead of using exceptions. -
Pattern matching:
the
matchexpressions distinguish betweenOkandErr. -
Exhaustive matching:
both variants of the returned
Resultare handled, making the match exhaustive. -
Type safety:
the stack is specifically instantiated as
Stack<i32>, so values inserted into it must satisfy the stack's element type. -
Bounds safety:
the example delegates element removal to
Vecrather than performing unchecked indexing. -
Move semantics:
a successful
poptransfers the resulting element out of the collection rather than copying it implicitly.
Expected Behaviour
The three values are pushed in the order 10,
20, 30. Because the stack is last-in,
first-out, the first two successful calls to Stack_pop
produce:
30
20
If another pop is attempted after the stack is empty, the operation
produces Err(StackError::Empty) rather than performing an
invalid access.
Conformance Significance
This is useful as a conformance/example-program example because it
exercises the interaction between several parts of the specification
rather than testing an isolated feature. In particular, it combines
generic types, owned values, borrowing, mutable access, collection
semantics, deterministic destruction, Result-based error
handling, and exhaustive pattern matching.
CobaltC Compiler Implementation Roadmap
AI-Assisted Specification Execution Plan
If you were to leverage AI to create a production-ready implementation of the CobaltC language specification (incorporating recent errata and memory invariants), a structured, phased execution plan is essential to prevent context degradation and ensure correctness.
- Lexer and Parser: Instruct the AI to generate a recursive-descent parser (or grammar definition using tools like Lark or Pest) conforming strictly to CobaltC's lexical rules, case sensitivity, and block structures.
- AST Structuring: Direct the AI to define a comprehensive Abstract Syntax Tree (AST) covering nominal types (structs, enums), tuples, arrays, type aliases, and explicit memory modifier keywords.
- Symbol Resolution: Build a scoping and name-resolution pass to map declarations, functions, and module boundaries.
- Strict Type Constraints: Implement type-checking logic enforcing CobaltC's foundational semantic invariants - specifically ensuring that implicit conversions never silently strip nullability, violate mutability, or alter lifetime guarantees.
- Lifetime and Ownership Tracking: Because the specification permits intricate patterns like dynamic collection element borrowing, task the AI with building a constraint-based region inference engine.
- Borrow-Checker Logic: Program rules to validate that references (
T*,T*?) do not outlive their owners, tracking explicit ownership transfers and argument compatibility at every function call boundary.
- IR Lowering Pass: Direct the AI to write a translation pass converting the type-checked AST into an intermediate representation, targeting LLVM IR or Cranelift.
- ABI and Layout Resolution: Define platform-specific layout rules, struct padding, alignment, and calling conventions since the specification leaves these mechanical choices to the implementor.
- Incremental Prompting: Feed the specification to the AI module by module (syntax first, then type checking, then memory safety) rather than passing the entire document at once to prevent context degradation.
- Negative Test Generation: Have the AI generate a suite of test cases containing intentional spec violations (such as illegal implicit nullability stripping or lifetime violations) to iteratively validate the generated compiler's error-reporting accuracy.