Appendix VI - Cobalt Language Specification

Version 1.0 — Complete Draft

**Status:** Design Specification
**Language family:** C-like systems programming language
**Primary goals:** memory safety, predictable performance, C interoperability, explicit low-level escape hatches

Notation

Cobalt's syntax is intentionally familiar to programmers coming from C, C++, and Rust. Where appropriate, the specification uses notation similar to Rust for concepts such as references and lifetimes. This is intended to make the underlying relationships immediately recognisable; it does not imply that Cobalt's semantics are identical to Rust's.

1. Language Definition

Cobalt is a statically typed, compiled, C-like systems programming language.

Cobalt is designed to prevent the following classes of memory errors in safe code:

* integer overflow leading to invalid memory sizes
* integer truncation affecting memory operations
* allocation-size mismatches
* out-of-bounds indexing
* out-of-bounds pointer arithmetic
* invalid memory reads
* invalid memory writes
* stale-pointer access
* use-after-free
* double free
* invalid free
* freeing through the wrong allocator
* ownership confusion
* lifetime violations
* data races

The fundamental safety model is:

OBJECT
    +
LOCATION
    +
AMOUNT
    +
LIFETIME
    +
OWNERSHIP
    =
VALID MEMORY OPERATION

This directly reflects the central model developed throughout *The Wrong Memory*.


2. Design Philosophy

Cobalt deliberately resembles C in syntax.

A C programmer should recognize:

if (condition)
{
}

for (...)
{
}

while (...)
{
}

struct Point
{
    int x;
    int y;
};

Point p;

p.x = 10;

However, Cobalt changes the semantic defaults.

In particular:

Cobalt safe code:
    memory validity is the default.

Cobalt unsafe code:
    explicit programmer responsibility.

C interoperability:
    explicitly isolated boundary.

The language does not require programmers to manually reconstruct memory-safety proofs for ordinary operations.


3. Safety Modes

Cobalt has two semantic modes.

3.1 Safe

All ordinary Cobalt code is safe.

Safe code cannot:

* dereference arbitrary raw pointers
* perform unchecked pointer arithmetic
* access memory outside an object's bounds
* access an object after its lifetime
* free memory it does not own
* free memory twice
* perform unchecked integer operations that could invalidate memory-safety assumptions
* create data races through ordinary references

3.2 Unsafe

Unsafe operations require:

unsafe
{
    ...
}

An unsafe block is an explicit boundary.

Unsafe code may perform operations unavailable to safe code, including:

raw pointer dereference
raw pointer arithmetic
manual allocation
manual deallocation
FFI calls
representation-level access
uninitialized-memory operations
unchecked numeric operations

Unsafe code remains subject to the language's defined semantics. It does not mean "anything the CPU happens to do is valid."


4. Undefined Behavior

Cobalt safe code has no language-level undefined behavior resulting from ordinary memory misuse.

In particular, the following are never silently undefined in safe code:

integer overflow
out-of-bounds indexing
use-after-free
double free
invalid free
null dereference
dangling reference
invalid pointer arithmetic
data race

An operation either:

1. is proven valid,
2. produces a defined failure such as an error or panic,
3. or is rejected by the compiler.

Undefined behavior is permitted only within explicitly unsafe operations and only where the specification explicitly identifies an operation as having unsafe preconditions.


5. Compilation Pipeline

A conforming implementation conceptually performs:

source
  ↓
lexing
  ↓
parsing
  ↓
AST
  ↓
name resolution
  ↓
type checking
  ↓
constant evaluation
  ↓
ownership analysis
  ↓
borrow checking
  ↓
lifetime checking
  ↓
bounds checking analysis
  ↓
unsafe verification
  ↓
lowering
  ↓
code generation

Optimizations must preserve the observable semantics of valid programs.


6. Lexical Structure

Identifiers follow:

identifier:
    letter
    _
    identifier letter
    identifier digit

Examples:

buffer
packet_length
Point
MAX_SIZE

Keywords include:

as
break
const
continue
else
enum
extern
false
fn
for
if
impl
import
in
let
match
module
move
mut
public
return
static
struct
trait
true
type
unsafe
union
use
while

Implementations may reserve additional keywords beginning with `__`.


7. Primitive Types

Cobalt defines fixed-width integer types:

i8    i16    i32    i64    i128
u8    u16    u32    u64    u128

It also defines:

usize
isize

usize is an unsigned integer type whose width is sufficient to represent every valid object size on the target.

isize is the signed counterpart of usize, with the same width and a range suitable for representing signed offsets within the target's addressable object model.

Other primitive types:

bool
char
f32
f64

8. Integer Semantics

Integer operations are checked by default.

For example:

u32 x = UINT32_MAX;
u32 y = x + 1;

does not silently wrap.

The operation produces a defined overflow failure.

For operations where wrapping is explicitly intended, the programmer must request it:

u32 y = x.wrapping_add(1);

or:

u32 y = wrapping(x + 1);

depending on the library/API style.


9. Checked Arithmetic

The standard integer API provides:

checked_add()
checked_sub()
checked_mul()
checked_div()
checked_shl()
checked_shr()

Conceptually:

Result<u64, ArithmeticError> checked_mul(u64 a, u64 b);

The language also provides checked arithmetic operators where required by context.


10. Saturating Arithmetic

Saturating operations are explicit:

x.saturating_add(y);
x.saturating_sub(y);
x.saturating_mul(y);

11. Wrapping Arithmetic

Wrapping operations are explicit:

x.wrapping_add(y);
x.wrapping_sub(y);
x.wrapping_mul(y);

This prevents accidental wrapping from silently changing an allocation size, offset, or access range.

The need for this distinction follows directly from the integer-to-allocation failure chain described in Chapter 1.


12. Integer Conversions

Numeric conversions are explicit when they can lose information.

For example:

u64 x = ...;
u32 y = x;

is rejected if the conversion cannot be proven safe.

Instead:

u32 y = try_cast<u32>(x)?;

or:

u32 y = checked_cast<u32>(x);

may be used.

Lossy conversion is never implicit.


13. Widening Conversions

A conversion that is mathematically lossless may be implicit where the target type can represent every source value.

Example:

u32 x = 10;
u64 y = x;

is permitted.


14. Signed/Unsigned Conversion

Signed and unsigned conversions are never performed implicitly when they can change the value.

This specifically prevents the class of bugs where a negative signed value becomes a very large unsigned size.


15. Memory-Sized Quantities

`usize` represents memory sizes and indexes.

The following APIs use `usize`:

allocation sizes
array indexes
slice lengths
slice offsets
object sizes
alignment values

A memory size cannot silently change meaning through a narrowing conversion.


16. Size and Alignment

The compiler provides:

sizeof(T)
alignof(T)

`sizeof(T)` is a compile-time constant for statically sized types.

Size calculations used by allocation operations must themselves be checked.


17. Checked Size Calculations

The standard library provides:

checked_size_mul(count, sizeof(T))
checked_size_add(a, b)

However, ordinary typed containers should make these APIs unnecessary in most code.

For example:

Vec<Point> points = Vec<Point>::with_capacity(count);

is preferable to:

malloc(count * sizeof(Point));

because the container owns the relationship between element count and byte capacity.


18. Objects

Every stored value belongs to an object.

An object has:

type
storage
size
alignment
lifetime
ownership state

A pointer or reference does not itself create an object.

An address existing in the machine address space does not imply that a Cobalt object exists there.

This explicitly preserves the distinction between:

address exists

and:

object exists

highlighted in the source material.


19. Stack Values

Local values normally have automatic storage:

fn process()
{
    Point p;
}

`p` remains alive until its scope ends unless moved earlier.


20. Heap Values

Heap ownership is represented by an owning type.

Primary owning pointer:

Box<T>

Example:

Box<Point> p = Box<Point>::new(Point{});

The owner is responsible for destroying the object.


21. No General `free()`

Safe Cobalt code does not expose:

free(pointer);

for ordinary owned objects.

Instead:

Box<Point> p = ...;

automatically destroys the object when its owner leaves scope.

This makes allocation and destruction a matched type-level relationship rather than an unrelated pair of function calls.

The source chapters identify wrong allocator, double free, premature free, interior-pointer free, and wrong-owner free as distinct failure modes.


22. Ownership

Every non-copyable value has exactly one logical owner.

Example:

Box<Data> a = create();

Box<Data> b = move a;

After the move:

b owns Data
a is moved-from

Using `a` afterward is rejected.


23. Copy Semantics

A type is copyable only if its definition explicitly permits copying.

Primitive scalar values are normally copyable.

Resource-owning types are normally move-only.

For example:

Box<File> a = ...;
Box<File> b = a;

is invalid.

Instead:

Box<File> b = move a;

transfers ownership.


24. Destruction

Every owning value has a deterministic destruction point.

Conceptually:

construct
    ↓
owned
    ↓
move / borrow
    ↓
last owner leaves scope
    ↓
destructor
    ↓
lifetime ends

Destructors run exactly once for each owned object.


25. Lifetime

Every object has a lifetime:

creation ---------------- destruction

A reference must not outlive the object it refers to.

This turns temporal memory safety into a language rule rather than a programmer convention. The source material explicitly describes lifetime validity as a temporal analogue of spatial bounds.


26. References

Cobalt has:

&T
&mut T

Example:

fn print_point(&Point p)
{
    ...
}

A shared reference:

& T

permits reading but not mutation.

A mutable reference:

&mut T

permits mutation.


27. Borrowing

Borrowing does not transfer ownership.

fn inspect(&Data data)
{
    ...
}

Data d = create();

inspect(&d);

// d still belongs to caller

28. Mutable Borrowing

fn update(&mut Data data)
{
    data.value += 1;
}

At most one active mutable reference may exist for a given object region.

A mutable reference cannot coexist with incompatible shared references.


29. Borrow Checker

The compiler verifies:

references remain within their source lifetime
mutable aliases do not conflict
moved values are not subsequently used
destroyed objects have no live references

The programmer therefore does not need manual lifetime bookkeeping for normal code.


30. Lifetime Elision

Common lifetimes need not be written explicitly.

Example:

fn first(&Slice<int> values) -> &int;

The compiler infers that the returned reference is tied to `values`.

When inference is ambiguous, an explicit lifetime may be required.


31. Explicit Lifetimes

Cobalt may use C-like lifetime syntax:

fn first<'a>(&'a Slice<int> values) -> &'a int;

Lifetimes are compile-time entities.

They do not necessarily exist at runtime.


32. References Are Not Integers

A reference cannot be converted into an integer in safe code.

This prevents the language from reducing an object identity to an arbitrary address.


33. Raw Pointers

Cobalt supports:

*const T
*mut T

Raw pointers exist primarily for:

FFI
device memory
custom allocators
OS interfaces
hardware access
low-level runtime code

They are unsafe to dereference.


34. Raw Pointer Creation

A reference may be converted to a raw pointer:

*const T p = raw(&value);

This does not extend the lifetime of `value`.


35. Raw Pointer Dereference

Raw pointer dereference requires unsafe:

unsafe
{
    value = *p;
}

The programmer is responsible for proving:

pointer is valid
object is live
location is aligned
object type is correct
access is permitted
access size is valid

36. Pointer Arithmetic

Safe Cobalt does not permit arbitrary raw pointer arithmetic.

Instead of:

p + offset

safe code uses slices or indexed views.

For example:

Slice<int> values = ...;

int x = values[index];

The compiler owns the relationship between:

base
element size
length
index

This directly addresses the source chapter's observation that an address being calculable does not imply that the corresponding object belongs to the allocation.


37. Pointer Arithmetic in Unsafe Code

Unsafe code may perform:

p.add(offset)

but the resulting pointer is only valid under explicit conditions.

The implementation must define pointer arithmetic in terms of the allocation/object from which the pointer originated.

Integer-to-pointer arithmetic is not permitted to manufacture a valid Cobalt reference.


38. Arrays

Fixed-size arrays use:

T[N]

Example:

int[10] values;

The compiler knows the exact element count.


39. Array Indexing

values[index]

is bounds checked.

The compiler may eliminate a check only when it can prove:

0 <= index < N

If the condition cannot be proven statically, a runtime bounds check is inserted.


40. Slice

A slice is a non-owning view:

Slice<T>

It represents:

object
starting position
element count
lifetime
access permissions

A slice does not own its elements.


41. Mutable Slice

MutSlice<T>

is a mutable view.

Example:

fn clear(MutSlice<u8> data)
{
    for (usize i = 0; i < data.len(); i++)
    {
        data[i] = 0;
    }
}

The slice itself establishes the valid range.


42. Slice Invariant

For a slice:

0 <= index < length

and:

length <= available object elements

must hold.

The programmer cannot construct an ordinary safe slice with a larger logical range than the underlying object.


43. Sub-Slicing

Slice<T> tail = values.subslice(start, count)?;

The operation verifies:

start <= length
count <= length - start

The subtraction-first form avoids overflow in:

start + count

and mirrors the defensive invariant emphasized by the source material.


44. Capacity and Length

Dynamic containers distinguish:

length
capacity

`length` is the number of initialized logical elements.

`capacity` is the number of elements for which storage has been reserved.

They are never interchangeable.

The source material explicitly identifies confusion between logical length and physical capacity as a recurring source of errors.


45. Vector

The standard dynamic array is:

Vec<T>

Example:

Vec<int> values;

values.push(10);
values.push(20);

The vector maintains:

pointer
length
capacity
element type
allocation ownership

as one abstraction.


46. Vector Growth

A vector's growth algorithm must establish:

new_capacity >= required_length

and:

new_capacity * sizeof(T)

must be representable.

Growth arithmetic cannot silently wrap.


47. Reserve

values.reserve(additional)?;

establishes enough capacity for `additional` new elements before insertion.

The conceptual invariant is:

additional <= capacity - length

after successful reservation.

This centralizes the arithmetic and allocation relationship rather than scattering checks throughout callers.


48. Reallocation

A vector may move its underlying storage when it grows.

Any references or slices into the vector are therefore invalidated unless the operation's API explicitly guarantees otherwise.

The borrow checker prevents use of invalidated references.


49. Memory Copying

Cobalt does not expose raw `memcpy` as the normal copying primitive.

For typed objects:

copy(destination, source);

requires compatible types and ranges.

For byte ranges:

copy_bytes(destination, source);

requires both source and destination byte ranges to be valid.


50. Copy as Range-to-Range Operation

A memory copy is defined as:

source range
        ↓
destination range

not:

pointer A
        ↓
pointer B

Both ranges must be established.

This follows the central lesson of Chapter 5.


51. Typed Copy

For:

copy(dst, src);

the compiler verifies:

source contains enough initialized T objects
destination contains enough writable T slots
source and destination lifetimes are valid
types are compatible

52. Byte Copy

Byte-level copying uses:

ByteSlice
MutByteSlice

rather than arbitrary pointer-plus-length combinations.

Example:

copy_bytes(dst.bytes(), src.bytes());

The ranges themselves carry the available size.


53. Copy Length

A normal safe copy does not accept an independent arbitrary length:

memcpy(dst, src, attacker_length);

Instead:

copy(dst, src);

derives the amount from the validated ranges.

If copying a deliberately smaller amount:

copy_prefix(dst, src, count)?;

the operation checks both:

count <= source.length
count <= destination.length

The source material identifies both source and destination bounds as necessary.


54. Overlapping Copy

For potentially overlapping ranges:

move_range(dst, src);

is used.

For non-overlapping ranges:

copy(dst, src);

may provide stronger optimization guarantees.


55. Reading Memory

A read requires a valid source range.

For example:

u32 value = read_u32(input)?;

requires enough bytes in `input`.

The parser cannot merely trust a length field contained in the input.

A length field is treated as a claim until validated against the actual available range.


56. Typed Reads

Reading a typed object from bytes requires:

sufficient size
alignment if required
valid representation
correct type
initialized storage

A buffer containing enough bytes does not automatically become a valid object of every type.

This distinction is explicitly highlighted in Chapter 6.


57. Uninitialized Memory

Cobalt distinguishes:

allocated
initialized
live

storage.

An allocation may contain storage that has not yet been initialized as a `T`.

Safe code cannot read an uninitialized `T`.


58. MaybeUninit

Low-level code may use:

MaybeUninit<T>

to represent allocated but uninitialized storage.

Example:

MaybeUninit<T> slot;

A value can only be converted to `T` after initialization has been established.


59. Writing Memory

A typed write requires a valid writable destination.

buffer[index] = value;

is valid only if:

buffer is live
index is in bounds
destination is writable
value has compatible type

60. Complete Write Range

For a write beginning at offset `o` with size `n`, validity requires:

o <= capacity
n <= capacity - o

rather than calculating:

o + n

first and hoping it does not overflow.

This captures the complete-range requirement described in Chapters 1 and 7.


61. Location Safety

Every safe memory operation has an object identity.

The language does not define safety merely in terms of numerical addresses.

A valid operation must establish:

intended object
+
location within object
+
valid range

A correct size at an incorrect location is still invalid.

Chapter 8 explicitly identifies wrong base, wrong offset, wrong unit, wrong index, overflow, stale pointer, and wrong object as independent ways to obtain a wrong location.


62. Units

Memory-related quantities have semantic units.

Examples:

elements
bytes
bits
characters
records

The type system should prevent accidental interchange where practical.

For example:

ElementCount
ByteCount

may be distinct standard-library types for APIs where unit confusion would be dangerous.


63. Object Identity

Every allocation has a unique logical identity.

An address does not identify an allocation independently of its lifetime.

After destruction:

old pointer

does not become a pointer to a new object merely because an allocator later reuses the same numerical address.


64. Use After Free

Safe Cobalt cannot produce:

Box<T> p = ...;

&T ref = p.borrow();

drop(p);

use(ref);

because `ref` prevents destruction while it remains live.


65. Ownership and Destruction

An object may be destroyed only when:

its owner is being destroyed

or through an explicitly defined ownership-consuming operation.

No ordinary function may arbitrarily destroy an object through a borrowed reference.


66. Reference Counting

Shared ownership is represented by an explicit type:

Rc<T>
Arc<T>

`Rc<T>` is for single-threaded shared ownership.

`Arc<T>` is for thread-safe shared ownership.

The reference-count implementation guarantees that the object remains alive while valid owners exist.

Reference counting does not automatically solve cycles; cyclic structures require weak references or another strategy.

The source material identifies explicit lifetime management as the purpose of reference counting.


67. Weak References

Weak<T>

does not keep an object alive.

Upgrading a weak reference produces:

Option<StrongReference>

rather than an invalid reference.


68. Nullability

Ordinary references cannot be null.

Optional references use:

Option<&T>
Option<&mut T>

Pointers that may be absent use:

Option<*const T>
Option<*mut T>

or a dedicated nullable pointer representation where ABI requires it.


69. No Null Dereference in Safe Code

Because ordinary references are non-null, this is impossible in safe code:

*T

where `T` is absent.


70. Structs

C-like structures:

struct Point
{
    int x;
    int y;
}

Fields are initialized according to the type's initialization rules.

Uninitialized fields are not silently permitted.


71. Enums

Enums provide tagged alternatives:

enum Result<T, E>
{
    Ok(T),
    Err(E)
}

The compiler ensures that only valid variants are constructed.


72. Tagged Unions

Cobalt may provide:

union Value
{
    int i;
    float f;
}

but safe access requires a valid active representation.

For unrestricted representation-level union access, unsafe code may be required.


73. Type Representation

Every type has:

size
alignment
layout
valid bit patterns

The compiler may add padding according to target ABI rules.

The language does not permit arbitrary reinterpretation of a value as another type merely because their sizes match.


74. Bit-Level Access

Explicit byte/bit operations are provided through:

Byte
ByteSlice
bit_cast<T>()

`bit_cast` is legal only when both types have compatible representation requirements.


75. `char` and Strings

Strings are not raw C character arrays.

Primary types:

String
StringView

A `String` owns its contents.

A `StringView` borrows them.

UTF-8 validity is maintained by the string abstraction.

Raw bytes use:

ByteSlice

rather than `StringView`.


76. Functions

Functions use C-like syntax:

fn add(int a, int b) -> int
{
    return a + b;
}

Function types are first-class where supported.


77. Error Handling

Recoverable failures use:

Result<T, E>

Optional values use:

Option<T>

The `?` operator propagates errors:

fn load() -> Result<Data, Error>
{
    Data d = read_data()?;
    return Ok(d);
}

78. Panics

A panic is a controlled runtime failure.

Examples include:

unrecoverable bounds failure
explicit panic
assertion failure

Implementations may configure panic behavior as:

abort
unwind

subject to ABI/runtime restrictions.


79. Bounds Failure

A dynamic bounds violation in safe code produces a defined failure.

It cannot become arbitrary memory corruption.


80. Compile-Time Bounds Elimination

The compiler may remove a runtime bounds check when static analysis proves the index valid.

For example:

for (usize i = 0; i < values.len(); i++)
{
    process(values[i]);
}

may compile without repeated bounds checks.

Optimization must preserve semantics.


81. Generics

Cobalt supports parametric generics:

struct Box<T>
{
    T value;
}

Generic constraints use traits:

fn print<T: Display>(T value)
{
    ...
}

82. Traits

Traits define behavior:

trait Display
{
    fn display(&self) -> String;
}

Implementations:

impl Display for Point
{
    fn display(&self) -> String
    {
        ...
    }
}

83. Generic Memory Safety

Generic code receives the same memory guarantees as concrete code.

A generic function cannot assume that:

T is Copy
T has trivial destruction
T has a fixed runtime size
T may be aliased

unless its constraints establish those properties.


84. Sized and Dynamically Sized Types

Types may be:

Sized
Dynamically sized

A generic parameter is `Sized` by default unless explicitly declared otherwise.


85. `unsafe` Trait Contracts

A trait may declare operations requiring an unsafe implementation.

Example:

unsafe trait RawAllocator
{
    ...
}

The implementation is responsible for satisfying the documented invariants.


86. Modules

Modules use:

module graphics
{
    ...
}

Imports:

import graphics::Point;

Aliases:

import graphics as gfx;

Visibility is explicit:

public struct Point
{
    ...
}

87. Name Resolution

Name resolution occurs before type checking.

Each local binding has a unique compiler identity.

Thus:

let value = 10;

{
    let value = 20;
    use(value);
}

use(value);

contains two distinct bindings.


88. Visibility

Declarations are private by default.

Public declarations require:

public

A public API cannot expose inaccessible private implementation types.


89. Pattern Matching

Cobalt supports:

match result
{
    Ok(value) => process(value),
    Err(error) => report(error)
}

Pattern bindings are scoped to their match arm.

The compiler verifies exhaustiveness where required.


90. `let`

Local declarations:

let value = expression;
let mut value = expression;

A mutable binding permits reassignment.

Mutability is distinct from ownership.


91. Assignment

Assignment:

value = expression;

is permitted only when `value` is mutable.

Assignment to a borrowed shared reference is forbidden.


92. Move Expression

Explicit moves use:

move value

Some contexts may infer moves automatically when the type is move-only.


93. Closures

Closures capture variables according to ownership rules.

A closure may capture by:

shared borrow
mutable borrow
move

The compiler selects or requires the appropriate capture mode.

A closure cannot outlive a borrowed variable it captures.


94. Iteration

Iteration uses:

for (item in collection)
{
    ...
}

The iterator API defines whether iteration:

moves elements
borrows elements
mutably borrows elements

95. Concurrency

Cobalt's concurrency model is ownership-aware.

A value may be transferred between threads only when its type satisfies the required thread-safety traits.


96. Data Races

Safe Cobalt cannot contain a data race.

The compiler prevents unsynchronized concurrent mutable access.

Shared mutable state requires explicit synchronization.


97. Atomics

The standard library provides:

Atomic<T>
AtomicBool
AtomicInt
AtomicPtr

where supported by the target.

Memory ordering is explicit:

relaxed
acquire
release
acq_rel
seq_cst

98. Mutexes

Synchronization primitives provide scoped guards:

lock(mutex)
{
    shared_state.update();
}

The guard automatically releases the lock when leaving scope.


99. FFI

Cobalt supports C interoperability through:

extern "C"
{
    fn c_function(...);
}

FFI calls are unsafe unless wrapped by a safe abstraction whose invariants have been established.


100. C Pointers

C pointer parameters are represented explicitly:

extern "C" fn process(*mut CData);

The compiler does not assume that an arbitrary C pointer is safe.


101. C Strings

C strings use explicit types:

CStr
CString

rather than treating arbitrary `char*` as safe strings.

A C string wrapper must establish:

valid address
valid lifetime
terminating NUL
maximum search range

102. FFI Ownership

FFI declarations must specify ownership where necessary.

For example, APIs may distinguish:

borrowed pointer
owned pointer
callee-owned result
caller-owned result

Safe wrappers must encode those rules.


103. Foreign Allocation

Memory allocated by a foreign allocator cannot be released using an unrelated Cobalt allocator.

The corresponding release function must be used.

This prevents allocator-mismatch bugs identified in the source material.


104. Allocator API

Advanced code may define:

trait Allocator
{
    fn allocate(Layout layout) -> Result<Allocation, AllocError>;
    fn deallocate(Allocation allocation);
}

The `Allocation` object records the allocation identity and layout.

An interior pointer cannot be supplied where an `Allocation` is required.


105. Allocation Identity

An allocation consists conceptually of:

allocation_id
base
size
alignment
allocator
lifetime

The allocator may use any physical representation.

The logical identity is maintained by the language/runtime model.


106. Deallocation

An allocation may be deallocated only when:

caller owns allocation
allocation is live
allocator matches
pointer/handle identifies the allocation itself
no required references remain

Double deallocation is rejected by ownership rules in safe abstractions.


107. Secure Erasure

Destroying an object does not imply that its physical bytes are erased.

Explicit secure erasure is a separate operation:

secure_zero(bytes);

The source material explicitly distinguishes lifetime termination from physical erasure.


108. Memory-Mapped and Device Memory

Device and memory-mapped regions are represented by explicit unsafe abstractions.

The implementation may provide:

Volatile<T>
Mmio<T>

to prevent ordinary compiler transformations from violating hardware semantics.


109. Volatile

`volatile` access is not a general memory-safety mechanism.

It specifies observable access behavior for special memory.

It does not disable:

bounds checking
ownership
lifetimes
type checking

110. Compiler Intrinsics

Compiler intrinsics may exist for:

SIMD
atomics
CPU instructions
memory barriers
ABI operations

Unsafe intrinsics require explicit unsafe context unless wrapped in a safe abstraction.


111. Constants

Constants:

const usize MAX_PACKET = 4096;

must be initialized with compile-time evaluable expressions.


112. Compile-Time Arithmetic

Compile-time arithmetic is checked.

An overflowing constant expression is a compile-time error unless explicitly using a wrapping operation.


113. Array Dimensions

Array dimensions must be representable and non-negative.

For:

T[N]

the compiler must ensure:

N * sizeof(T)

is representable.


114. Layout Overflow

A type whose layout cannot be represented on the target is rejected.

This includes:

array size overflow
struct layout overflow
alignment overflow
padding overflow

115. Struct Layout

By default, the compiler controls field layout.

An explicit C-compatible layout may be requested:

@repr(C)
struct Header
{
    u32 length;
    u16 type;
}

116. Packed Layout

Packed structures require explicit annotation:

@repr(packed)

Access to misaligned fields may require special operations or unsafe code.

The compiler must not silently create invalid aligned references.


117. Serialization

Safe serialization APIs operate on explicit byte ranges.

A serializer establishes:

output capacity
encoded size
representation validity

before writing.


118. Deserialization

Deserialization treats external lengths and counts as untrusted.

For:

u32 length = read_u32(input)?;

`length` is initially merely an input claim.

The parser must establish its relationship to:

remaining input
available output
maximum permitted size

before using it as a memory boundary.

This is a direct application of the source's "length fields are claims, not facts" principle.


119. Arithmetic-to-Memory Invariant

Whenever a quantity influences memory, the compiler and/or API must preserve the relationship:

external value
    ↓
interpretation
    ↓
conversion
    ↓
arithmetic
    ↓
size / offset
    ↓
object
    ↓
range
    ↓
memory operation

An earlier validation does not automatically validate a later transformed quantity.


120. Security-Critical Quantities

The following are treated as memory-relevant:

count
length
size
capacity
offset
stride
index
width
height
element_size
alignment

Their types and transformations must remain explicit.

The source material repeatedly emphasizes following such quantities through the complete chain rather than inspecting individual operations in isolation.


121. Complete Memory Operation Rule

Every safe memory operation must establish five properties:

1. OBJECT
   Which object is involved?

2. LOCATION
   Where within that object?

3. AMOUNT
   How much memory?

4. LIFETIME
   Is the object currently alive?

5. OWNERSHIP / AUTHORITY
   Does this operation have the right to access it?

This is the fundamental Cobalt memory rule.


122. Read Rule

A read is valid only if:

source object is live
source location is within object
source range is readable
source representation is valid
source value is initialized

123. Write Rule

A write is valid only if:

destination object is live
destination location is within object
destination range is writable
destination representation is valid
caller has appropriate ownership/borrow authority

124. Copy Rule

A copy is valid only if:

source object is live
destination object is live
source range is readable
destination range is writable
source amount is valid
destination amount is sufficient
types/representations are compatible
overlap rules are satisfied

125. Address Rule

A pointer/reference derivation is valid only if:

base identifies a live object
offset is expressed in the correct unit
offset is representable
result remains within the permitted object range

A pointer calculation cannot create authority to access another object.


126. Lifetime Rule

An access is invalid if:

object lifetime ended

regardless of whether:

old address still contains bytes

or:

allocator has reused the address.

127. Ownership Rule

Destruction is valid only if:

caller owns the object

or:

caller possesses an explicitly defined destruction capability.

A borrowed reference never implies ownership.


128. Bounds Rule

For a range:

[start, start + length)

the compiler/runtime must establish:

start <= capacity
length <= capacity - start

rather than relying on an unchecked sum.


129. Integer-to-Memory Rule

A checked integer operation does not automatically make a memory operation safe.

The resulting quantity must still correspond to the actual object's capacity.

Likewise, a valid allocation does not prove that later code is using the allocation correctly.

This distinction is central to the source material's treatment of allocators.


130. Allocation Invariant

For a container:

length <= capacity

and:

capacity * sizeof(T) <= maximum representable allocation size

must hold.


131. Mutation Invariant

If an operation adds `additional` elements:

Without reallocation:

additional <= capacity - length

With reallocation:

new_capacity >= length + additional

and the new capacity must fit the target representation.


132. No Independent Metadata Obligation

A safe container should not expose mutable independent:

pointer
length
capacity

fields to ordinary code.

These values must be maintained together by the container abstraction.

This is a deliberate structural response to the source material's recommendation to centralize resizing and preserve related invariants.


133. Borrowed Range Invalidation

Operations that can invalidate storage must be rejected while incompatible borrows exist.

Example:

Vec<int> v;

Slice<int> s = v.slice();

v.push(10);      // potentially reallocating

use(s);          // rejected

134. Stable Storage

A container may explicitly provide stable references:

StableVec<T>

if its implementation guarantees that inserting elements does not invalidate existing object locations.


135. Iterator Invalidation

Every collection API must document which operations invalidate:

references
slices
iterators
pointers

The compiler must enforce invalidation rules where they can be represented statically.


136. Aliasing

Safe mutable access follows:

many readers
OR
one writer

but not both simultaneously for overlapping memory.

This applies to:

references
slices
iterators
views

137. Partial Borrowing

Cobalt may borrow disjoint fields independently:

&mut point.x
&mut point.y

because the compiler can prove that the memory regions do not overlap.


138. Array Element Borrowing

Distinct array elements may be mutably borrowed simultaneously when the compiler can establish distinct indexes.

For dynamically computed indexes, an API may require a checked splitting operation:

(left, right) = slice.split_at(index)?;

139. Unsafe Escape Hatch

The language must make unsafe operations visually obvious:

unsafe
{
    *raw = value;
}

Unsafe code cannot silently contaminate an entire module.

The boundary is lexical and reviewable.


140. Unsafe Contracts

An unsafe function documents preconditions.

Example:

unsafe fn from_raw_parts(*mut T pointer, usize length) -> MutSlice<T>;

Its contract may require:

pointer identifies a live allocation
pointer is correctly aligned
length does not exceed allocation capacity
elements are initialized

The compiler does not prove these conditions; the caller assumes responsibility.


141. Safe Wrappers

Unsafe functionality should normally be encapsulated:

public fn parse_packet(ByteSlice input) -> Result<Packet, ParseError>
{
    ...
}

The unsafe portion is minimized and surrounded by checked invariants.


142. Unsafe Does Not Mean Unspecified

Every unsafe operation still has specified behavior where its preconditions are satisfied.

Violating an unsafe precondition constitutes a programmer error outside the guarantees of safe Cobalt.


143. Diagnostics

Diagnostics should identify the violated invariant.

For example:

error[E0421]: index out of bounds

  values[index]
        ^^^^^

index has type usize
index may be 10
values contains 10 elements
valid indexes are 0..10

144. Use-After-Move Diagnostic

Example:

error[E0310]: use of moved value

  Box<Data> b = move a;
                    ^ value moved here

  use(a);
      ^ value used after move

145. Borrow Diagnostic

Example:

error[E0412]: cannot mutably borrow while shared borrow exists

Diagnostics should identify both borrow sites.


146. Integer Overflow Diagnostic

Compile-time:

error[E0201]: integer overflow in constant expression

Runtime checked arithmetic produces a defined arithmetic failure rather than memory corruption.


147. Invalid Conversion Diagnostic

error[E0204]: narrowing conversion requires explicit checked conversion

The compiler should explain the possible range loss.


148. Unsafe Diagnostic

Attempting:

*p

outside unsafe code produces:

error[E0501]: raw pointer dereference requires unsafe context

149. Invalid Free Diagnostic

Safe APIs should make ordinary invalid frees unrepresentable.

If low-level allocator APIs are used incorrectly:

error: allocation ownership does not match deallocation

where statically detectable.


150. Standard Library Memory Types

A conforming standard library should provide at least:

Box<T>
Rc<T>
Arc<T>
Weak<T>

Slice<T>
MutSlice<T>

Vec<T>

String
StringView

ByteSlice
MutByteSlice

MaybeUninit<T>

Result<T,E>
Option<T>

151. Standard Checked Memory Operations

The standard library should provide safe equivalents of common C operations:

copy
move_range
fill
compare
search
split
subslice
read
write

These APIs operate on typed ranges rather than independent raw pointers and lengths wherever possible.


152. C Library Compatibility

Cobalt may expose wrappers:

c_memcpy
c_memmove
c_memset

but they remain unsafe when called with raw pointers.

The preferred API is the Cobalt range-aware equivalent.


153. No Safe `memcpy(void*, void*, size_t)`

A generic function with the conceptual signature:

memcpy(void* dst, void* src, usize size)

cannot be a safe primitive.

Its safety requires independent proofs of:

source validity
destination validity
source size
destination size
size arithmetic
lifetime
aliasing

The language therefore uses richer types to carry those facts.


154. Memory Safety and Performance

Memory safety does not require every access to perform a runtime check.

The implementation may use:

static proofs
range analysis
loop analysis
inlining
ownership analysis
bounds-check elimination

to remove checks.

The semantic guarantee remains unchanged.


155. Zero-Cost Principle

When a safety property is statically provable, no runtime cost is required for that property.

Example:

for (usize i = 0; i < array.length; i++)
{
    sum += array[i];
}

may compile to the same machine-level loop a careful C programmer would write.


156. Runtime Checks

When a property cannot be proven statically, the implementation may insert a runtime check.

Failure is defined.

It cannot become memory corruption.


157. Optimizer Rule

The optimizer may assume all safe-language invariants hold.

It may not transform a safe program into one whose behavior violates those invariants.


158. Sanitizers

Implementations should provide optional instrumentation for:

bounds
ownership
borrow/lifetime
integer overflow
uninitialized memory
FFI boundaries

Sanitizers are diagnostic tools, not substitutes for the language's safety model.


159. Fuzzing

The standard toolchain should support fuzzing of:

parsers
deserializers
allocators
unsafe wrappers
FFI boundaries

The language's defined failure semantics make fuzzing failures distinguishable from arbitrary memory corruption.


160. Formal Memory Model

The abstract memory model is:

Allocation
{
    identity
    type/representation
    base
    size
    alignment
    lifetime
    permissions
}

An access consists of:

object
location
amount
access mode

An access is valid only if all corresponding constraints hold.


161. Access Modes

Memory permissions are conceptually:

Read
Write
ReadWrite
Move
Destroy

A borrow grants only the minimum required permission.


162. Read Permission

A shared reference grants:

Read

for its lifetime.


163. Write Permission

A mutable reference grants:

ReadWrite

for its lifetime.

It excludes incompatible aliases.


164. Move Permission

Ownership grants:

Move
Destroy

subject to the object's type semantics.


165. Destroy Permission

Only the owner, or an explicitly designated destruction mechanism, may end the object's lifetime.


166. Lifetime + Spatial Safety

A valid access must satisfy both:

spatial:
    location + amount is inside object

temporal:
    object is alive

Neither property substitutes for the other.

This is the combined model explicitly developed in Chapter 9.


167. Security Boundary

Cobalt considers these independent security properties:

arithmetic correctness
allocation correctness
object identity
location correctness
range correctness
initialization correctness
lifetime correctness
ownership correctness

A program must preserve all of them.


168. First-Broken-Invariant Principle

Compiler diagnostics, static analyzers, and security tooling should prefer identifying the earliest violated invariant.

For example:

overflow
    ↓
wrong allocation
    ↓
wrong pointer
    ↓
wrong range
    ↓
out-of-bounds write

The preferred diagnostic is the earliest provable violation, not merely the final crash.

This reflects the central analytical approach of the source material.


169. Security Invariant Ledger

For complex APIs, implementations may expose contracts such as:

count:
    source = input
    type = usize
    invariant = count <= max_count

size:
    derived = count * sizeof(T)
    invariant = representable

allocation:
    size = size

offset:
    derived = index * sizeof(T)
    invariant = offset <= capacity

access:
    amount = sizeof(T)
    invariant = amount <= capacity - offset

This is especially useful for static analysis and formal verification.


170. API Design Principle

Cobalt libraries should prefer:

types that carry invariants

over:

integers that require callers to remember invariants.

Prefer:

ByteSlice

over:

void* + usize

Prefer:

Vec<T>

over:

T* + length + capacity

Prefer:

Box<T>

over:

T* + manually managed lifetime

171. Security-Critical API Principle

An API should make invalid states difficult or impossible to represent.

For example:

copy(dst, src);

is preferable to:

memcpy(dst.ptr, src.ptr, length);

because the former keeps:

object
location
amount
lifetime
ownership

closer together.


172. C-Like Syntax, Non-C-Like Semantics

Cobalt intentionally retains:

if (...)
for (...)
while (...)
struct
enum
fn
return
x[i]
x.field
x->field

but changes their relationship to memory.

In particular:

[]        → checked range operation
&         → borrow
move      → explicit ownership transfer
Box<T>    → owning heap object
Slice<T>  → bounded borrowed range
*mut T    → explicitly unsafe raw pointer

173. Example: Safe Dynamic Buffer

struct Packet
{
    Vec<u8> data;
}

fn append(Packet&mut packet, ByteSlice input) -> Result<(), Error>
{
    packet.data.reserve(input.len())?;

    for (u8 byte in input)
    {
        packet.data.push(byte);
    }

    return Ok(());
}

There is no separate:

pointer
length
capacity
allocation size

that callers must keep synchronized.


174. Example: Safe Packet Parsing

fn parse(ByteSlice input) -> Result<Packet, ParseError>
{
    u32 count = input.read_u32()?;

    if (count > MAX_ENTRIES)
    {
        return Err(ParseError::TooManyEntries);
    }

    Vec<Entry> entries = Vec<Entry>::with_capacity(checked_cast<usize>(count))?;

    for (usize i = 0; i < count; i++)
    {
        Entry entry = parse_entry(input)?;
        entries.push(move entry);
    }

    return Ok(Packet{ entries });
}

The parser never treats an external count as automatically authoritative.


175. Example: Safe Copy

fn duplicate(ByteSlice input) -> Result<Vec<u8>, Error>
{
    Vec<u8> result = Vec<u8>::with_capacity(input.len())?;

    result.extend(input)?;

    return Ok(result);
}

There is no independent destination pointer plus arbitrary copy length.


176. Example: Safe Borrowing

fn first(ByteSlice input) -> Option<&u8>
{
    if (input.len() == 0)
    {
        return None;
    }

    return Some(&input[0]);
}

The returned reference cannot outlive `input`.


177. Example: Ownership Transfer

fn consume(Box<Resource> resource)
{
    resource.use();
}

fn process()
{
    Box<Resource> r = acquire();

    consume(move r);

    // r cannot be used here
}

The compiler enforces the transfer.


178. Example: Unsafe FFI

extern "C"
{
    fn c_process(*const u8, usize);
}

fn process(ByteSlice input)
{
    unsafe
    {
        c_process(input.ptr(), input.len());
    }
}

The safe wrapper is responsible for proving that the C function's contract is satisfied.


179. Example: Safe Wrapper Around Unsafe Code

fn call_c(ByteSlice input) -> Result<(), Error>
{
    if (input.len() > MAX_INPUT)
    {
        return Err(Error::TooLarge);
    }

    unsafe
    {
        c_process(input.ptr(), input.len());
    }

    return Ok(());
}

The unsafe region is intentionally small.


180. What Cobalt Prevents

In safe Cobalt, the following conceptual C patterns are rejected or replaced:

count * sizeof(T)
        ↓
wraparound
        ↓
small allocation
        ↓
large loop
        ↓
overflow
pointer + attacker_offset
        ↓
wrong location
        ↓
write
pointer + offset
        ↓
copy(pointer, length)
        ↓
offset + length > capacity
pointer
  ↓
free
  ↓
stale pointer
  ↓
use
pointer
  ↓
free
  ↓
free again
pointer
  ↓
wrong allocator
  ↓
release

These are not merely discouraged patterns; safe Cobalt's types and semantics are designed to prevent them.


181. What Unsafe Code Is For

Unsafe code exists for legitimate systems-level requirements:

operating-system interfaces
device drivers
custom allocators
hardware registers
FFI
specialized memory pools
SIMD
lock-free structures
runtime implementation
embedded systems

Unsafe code is not intended to be necessary for ordinary:

arrays
strings
network parsing
file parsing
serialization
dynamic containers
business logic

182. Unsafe Review Requirements

An unsafe function should document:

Safety:
    required caller invariants

Example:

// Safety:
// `ptr` must refer to a live allocation containing at least
// `len` initialized T objects and must be correctly aligned.
unsafe fn from_raw_parts(...) -> Slice<T>;

183. Conformance

A conforming implementation must:

1. reject programs violating static safety rules;
2. preserve ownership and lifetime semantics;
3. implement checked integer semantics;
4. enforce safe bounds;
5. prevent invalid safe memory access;
6. preserve Cobalt's defined failure behavior;
7. implement specified ABI rules;
8. provide required standard types and operations.


184. Implementation Freedom

The specification does not mandate:

reference counting vs tracing GC
specific CPU
specific allocator
specific register allocation
specific optimizer
specific garbage collector for future GC types

provided the observable semantics and safety guarantees remain intact.

Cobalt's core ownership model is deterministic and does not require garbage collection.


185. Garbage Collection

A future garbage-collected type system may be added, but GC-managed references must remain distinguishable from ordinary ownership and borrowing.

GC cannot be used to weaken:

bounds
type safety
data-race safety
representation validity

186. ABI Stability

Cobalt-native ABI is implementation-defined within a compiler release family unless explicitly marked stable.

C ABI compatibility is available through:

@repr(C)
extern "C"

and related annotations.


187. Versioning

Language versions follow:

MAJOR.MINOR

Breaking semantic changes require a major version.

Additive syntax or library features may use minor versions where compatibility is preserved.


188. Safety Compatibility

A compiler may not introduce a new language feature that silently weakens existing safe-code guarantees.

Any feature capable of bypassing those guarantees must be:

unsafe

or otherwise explicitly constrained.


189. Formal Safe-Code Guarantee

For every successfully compiled safe program, the implementation guarantees:

No out-of-bounds memory access
No use-after-free
No double free
No invalid free
No dangling reference
No null reference dereference
No safe raw-pointer dereference
No data race
No unchecked integer overflow
No invalid typed read
No invalid typed write

subject to explicitly documented implementation bugs, hardware faults, or violations at unsafe/foreign boundaries.


190. Complete Memory Safety Theorem — Informal

For a valid safe Cobalt memory operation:

Object O
Location L
Amount N
Lifetime T
Authority A

the compiler/runtime establishes:

O is live
L belongs to O
L + N belongs to O
the operation is permitted by A
the representation is valid
the access does not violate aliasing rules

Therefore the operation cannot access unrelated or dead memory through ordinary safe semantics.


191. Relationship to the Eleven Failure Classes

The language design maps to the source chapters as follows:

| Failure | Cobalt mechanism |
| ------------------------ | ---------------------------------------- |
| Integer overflow | checked arithmetic |
| Truncation | explicit checked conversion |
| Wrong allocation size | typed containers + checked layout |
| Wrong pointer arithmetic | bounded slices/restricted raw pointers |
| Wrong copy amount | range-based copy |
| Wrong read amount | validated source ranges |
| Wrong write amount | validated destination ranges |
| Wrong location | object-associated references/slices |
| Use-after-free | ownership + borrow checker |
| Double free | unique ownership |
| Invalid free | typed allocation ownership |
| Wrong allocator | allocator-associated allocation identity |
| Uninitialized read | initialization tracking |
| Data race | ownership + concurrency rules |

The source material's final model identifies these errors as interacting rather than independent categories; Cobalt therefore treats them as one memory model rather than eleven unrelated checks.


192. The Cobalt Memory Model in One Diagram

EXTERNAL DATA
                       │
                       ▼
                 typed value
                       │
                       ▼
             checked arithmetic
                       │
                       ▼
              size / count / offset
                       │
                       ▼
                  allocation
                       │
                       ▼
                    object
                       │
             ┌─────────┴─────────┐
             ▼                   ▼
          location             lifetime
             │                   │
             └─────────┬─────────┘
                       ▼
                    range
                       │
                       ▼
                 access rights
                       │
                       ▼
                  READ/WRITE

At every transition, Cobalt attempts to preserve the invariant.


193. Core Principle

Cobalt does not define memory as:

address + number of bytes

It defines memory as:

object
+
location
+
amount
+
lifetime
+
ownership

This is the fundamental design decision of the language.


194. Final Language Principle

The central rule of Cobalt is:

> **A value that describes memory must remain connected to the memory it describes.**

An integer is not automatically a size.

A size is not automatically an allocation.

An address is not automatically an object.

A pointer is not automatically valid.

A valid location is not automatically a valid range.

A valid range is not automatically live.

A reference is not ownership.

Ownership cannot be silently duplicated.

A successful allocation does not prove that later accesses are correct.

A successful arithmetic operation does not prove that its result describes the intended memory.

The language therefore attempts to preserve the relationship from:

value
  ↓
meaning
  ↓
object
  ↓
range
  ↓
lifetime
  ↓
authority
  ↓
access

through the type system, ownership system, borrow checker, runtime checks, and explicit unsafe boundaries.

That is the foundation of Cobalt 1.0.


← Previous Bibliography →