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.
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 fundamental borrowing rule is:
zero or more compatible shared borrows
OR
one mutable borrow
Conflicting borrows MUST be rejected.
A shared borrow is formed using the borrow operator:
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 borrowed value MUST NOT be moved while an active borrow would subsequently be used.
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 shared borrow provides read-only 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.
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 the same location 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 borrow MAY subsequently be used for shared access when the mutable borrow is no longer used in a conflicting manner.
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
The compiler MUST reject a reference that could be used after its referent has ceased to exist.
The lifetime of a borrow is not required to extend to the end of the lexical scope containing the reference. The compiler SHOULD infer the shortest valid lifetime consistent with all uses of the reference.
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.
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 borrows 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);
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`
}
When a returned reference is derived from a borrowed parameter, the compiler MUST ensure that the returned reference cannot outlive the source borrow.
fn first(String* value) -> String*
{
return value;
}
String value = "hello";
{
String* result = first(&value);
print(*result);
}
Ownership MUST NOT be inferred from a borrowed return value. Returning a reference returns 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.
Reborrowing MUST preserve the ownership 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 the reborrow is live, the original mutable 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 ends, 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, "?");
The compiler MUST reject overlapping field borrows when their locations cannot be established as disjoint.
mut Pair pair =
{
first: "one",
second: "two"
};
Pair* whole = &mut pair;
String* first = &mut pair.first;
use(*whole); // ERROR: conflicting borrow
Partial borrowing MUST preserve the same aliasing 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 value.
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, "?");
The ownership model therefore permits:
shared
shared
shared
for compatible shared access, but does not permit:
mutable
mutable
or:
shared
mutable
when the references provide conflicting access to the same location.
These aliasing requirements apply to direct references, reborrows, field borrows, function parameters, returned borrows, and collection element borrows.
50. Collection Borrowing
Elements of a collection MAY be borrowed.
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 one of its elements 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 has been invalidated or replaced.
Collection-specific borrowing rules MAY impose additional restrictions where required to preserve ownership, lifetime, aliasing, or storage validity.
51. Borrow Invalidation
A borrow becomes invalid for use when its referent ceases to exist, when the referenced storage is no longer valid, or when an operation conflicts with the borrow and the reference remains live.
A value MUST NOT be moved while a live borrow would subsequently access that value through the original ownership path.
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 reference is no longer used.
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);
A borrow MUST NOT be used after its referent has been destroyed.
String* reference;
{
String value = "hello";
reference = &value;
}
print(*reference); // ERROR: referent has been destroyed
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.
45. Aliasing
Safe code MUST NOT create an aliasing configuration that violates the ownership model.
A mutable access cannot coexist with an incompatible shared or mutable access.
This rule applies across library abstractions as well as direct language operations.
46. Collection Borrowing
If a collection operation may invalidate references into the collection, the operation MUST NOT occur while an incompatible borrow remains live.
For example:
mut Vec<i32> values = Vec<i32>::new();
i32* first = &values[0];
values.push(10);
is rejected when the operation may invalidate first.
47. Destruction
Owned values are destroyed deterministically.
An ownership responsibility is destroyed exactly once.
Moved-from ownership does not cause a second destruction.
48. 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.
49. 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.
50. Abort
An abort terminates execution immediately.
Normal destruction is not guaranteed after an abort.
51. 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.
52. 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.
53. Option<T>
The canonical optional-value type is:
enum Option<T>
{
Some(T),
None
}
Option<T> represents the presence or absence of a
value.
54. 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.
55. Error Propagation
The ? operator propagates a compatible error from the
current operation to the enclosing function.
It is not an exception mechanism.
56. 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.
57. 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.
58. 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.
59. Box<T>
Box<T> represents unique heap ownership.
Destroying the Box releases its owned allocation and
contained value according to normal destruction rules.
60. Rc<T>
Rc<T> provides reference-counted shared ownership
in contexts where its concurrency restrictions are satisfied.
Reference-counted cycles can prevent destruction.
61. 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.
62. Weak<T>
Weak<T> provides non-owning access to
reference-counted objects.
A weak reference does not keep its target alive.
63. 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.
64. 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.
65. 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.
66. RwLock
A read/write lock permits:
- multiple compatible readers; or
- one writer.
It MUST NOT simultaneously expose incompatible read and write access.
67. 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.
68. 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.
69. 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.
70. 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.
71. 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.
72. 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.
73. 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.
74. 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.
75. 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.
76. 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.
77. 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.
78. 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.
79. 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.
80. 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.
81. 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.
82. 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.
83. 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.
84. Implementation-Defined Behavior
Any implementation-defined property MUST be documented.
A compiler cannot claim conformance while silently choosing behavior contrary to a normative requirement.
85. 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.
86. 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.
87. 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.
88. 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.
89. 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.
90. 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.
91. 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
92. 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.
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.