> does not require tracing garbage collection
Just call it garbage collection.
(Applies in two ways: if you think that garbage collection subsumes reference counting then this language doesn’t require garbage collection in the sense that it also doesn’t require reference counting; if you think that garbage collection does not subsume reference counting then there’s no point in saying three words when you can say two.)
some just delegate more or less to the implementation
i read trying to assess if it's more or less expressible than rust's current nll or the future polonius and honestly there's nothing there
Name Size Signed
bool 1 no
ichar 8 yes
char 8 no
short 16 yes
ushort 16 no
int 32 yes
uint 32 no
long 64 yes
ulong 64 no
int128 128 yes
uint128 128 no
Source: https://c3-lang.org/language-overview/types/#integer-typesCobaltC is a statically typed systems programming language providing:
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.
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.
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.
CobaltC supports line comments:
// comment
and block comments:
/*
comment
*/
Comments have no semantic effect.
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.
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
CobaltC provides:
Numeric literals MAY use separators where supported by the implementation, provided separators do not alter their value.
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.
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.
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.
Constants use:
const i32 maximum = 100;
A constant initializer MUST satisfy the implementation's constant-expression requirements.
A constant cannot be mutated.
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.
CobaltC supports:
Structs and enums are nominal types.
Type aliases do not create new nominal types.
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.
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.
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.
Assignments, function arguments and return values MUST have compatible types.
Implicit conversions MUST NOT silently:
Explicit conversion facilities MAY be provided.
CobaltC permits inference where the language grammar and context establish a unique type.
Inference MUST preserve all semantic distinctions relevant to:
Inference MUST NOT make an unsafe operation appear safe.
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.
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.
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.
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)
}
Tuples combine a fixed number of values.
Tuple elements are independently typed.
Tuple ownership follows the ownership rules of their elements.
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.
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.
Expressions produce values or perform operations.
The core expression categories include:
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.
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.
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.
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.
A call is valid only if:
CobaltC provides:
if condition
{
...
}
else
{
...
}
The condition MUST satisfy the Boolean condition requirements.
CobaltC provides:
while
for
loop
break exits the applicable loop.
continue begins the next iteration.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Owned values are destroyed deterministically.
An ownership responsibility is destroyed exactly once.
Moved-from ownership does not cause a second destruction.
For ordinary scope exit:
An implementation MUST preserve the observable consequences of this ordering.
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.
An abort terminates execution immediately.
Normal destruction is not guaranteed after an abort.
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.
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.
The canonical optional-value type is:
enum Option<T>
{
Some(T),
None
}
Option<T> represents the presence or absence of a value.
The canonical recoverable-error type is:
enum Result<T,E>
{
Ok(T),
Err(E)
}
Expected operational failures SHOULD be represented using Result.
The ? operator propagates a compatible error from the current operation to the enclosing function.
It is not an exception mechanism.
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.
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.
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.
Box<T> represents unique heap ownership.
Destroying the Box releases its owned allocation and contained value according to normal destruction rules.
Rc<T> provides reference-counted shared ownership in contexts where its concurrency restrictions are satisfied.
Reference-counted cycles can prevent destruction.
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.
Weak<T> provides non-owning access to reference-counted objects.
A weak reference does not keep its target alive.
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.
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.
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.
A read/write lock permits:
It MUST NOT simultaneously expose incompatible read and write access.
Atomic operations are indivisible according to the specified atomic type and memory-order semantics.
Atomicity does not itself establish ownership or higher-level synchronization.
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.
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.
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.
Unsafe operations require an explicit unsafe context:
unsafe
{
...
}
Unsafe permits operations requiring programmer-supplied invariants.
It does not make an invalid operation intrinsically correct.
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.
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.
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.
Ownership crossing an FFI boundary MUST be defined by the API contract.
Possible contracts include:
The ABI alone does not determine ownership.
A target ABI profile specifies at minimum:
Binary compatibility is guaranteed only where compatible ABI profiles are used.
A hosted CobaltC program begins through main.
The runtime provides the facilities required by the language and standard library, including:
The internal runtime architecture is implementation-defined.
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.
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.
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.
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.
CobaltC's safety guarantees apply to conforming safe code.
They do not guarantee:
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.
Any implementation-defined property MUST be documented.
A compiler cannot claim conformance while silently choosing behavior contrary to a normative requirement.
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.
Requires the language syntax, type system, static semantics, ownership, borrowing, lifetimes and core safety guarantees.
Requires Core plus the mandatory standard-library baseline.
Requires Standard plus a complete declared runtime and ABI profile for the target.
An implementation claiming conformance MUST state its level.
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.
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.
CobaltC 1.0.0 is a closed language edition.
Changes after publication are classified as:
A semantic change MUST NOT be silently presented as CobaltC 1.0.0 behavior.
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:
Unsafe and foreign code lie outside these automatic guarantees.
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
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.
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;
}
The example intentionally exercises several of the normative semantic rules established by the CobaltC 1.0 specification.
Stack<T> is a generic nominal type and can be instantiated as Stack<i32>.Stack<i32> stack owns the stack value. The stack in turn owns its contained Vec<i32>.stack leaves its scope, its owned contents are destroyed according to CobaltC's deterministic destruction rules.&stack provides access to the existing stack without transferring ownership to Stack_push or Stack_pop.mut Stack<T>* parameter permits the called function to modify the borrowed stack while remaining subject to CobaltC's aliasing rules.Stack_is_empty accepts a non-mutating borrow because it only needs to inspect the stack.Stack_pop returns Result<T, StackError> instead of using exceptions.match expressions distinguish between Ok and Err.Result are handled, making the match exhaustive.Stack<i32>, so values inserted into it must satisfy the stack's element type.Vec rather than performing unchecked indexing.pop transfers the resulting element out of the collection rather than copying it implicitly.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.
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.