Final

CobaltC Programming Language Specification

CobaltC 1.0 — Specification version 1.0.0

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
1call, indexing, member access
2!, unary +, unary -, move, borrow
3*, /, %
4+, -
5<<, >>
6<, <=, >, >=
7==, !=
8&
9^
10|
11&&
12||
13assignment

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 as Stack<i32>.
  • Ownership: Stack<i32> stack owns the stack value. The stack in turn owns its contained Vec<i32>.
  • Deterministic destruction: when stack leaves its scope, its owned contents are destroyed according to CobaltC's deterministic destruction rules.
  • Borrowing: &stack provides access to the existing stack without transferring ownership to Stack_push or Stack_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_empty accepts a non-mutating borrow because it only needs to inspect the stack.
  • Result-based error handling: Stack_pop returns Result<T, StackError> instead of using exceptions.
  • Pattern matching: the match expressions distinguish between Ok and Err.
  • Exhaustive matching: both variants of the returned Result are 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 Vec rather than performing unchecked indexing.
  • Move semantics: a successful pop transfers 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.

Phase 1: Frontend and AST Generation
  • 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.
Phase 2: Semantic Analysis and Type Checking
  • 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.
Phase 3: The Static Analysis & Borrow Subsystem
  • 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.
Phase 4: Target Lowering and Code Generation
  • 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.
Phase 5: Iterative Test-Driven Implementation
  • 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.