Documentation

kama type model — value / resource / view / enum / contract / intrinsic

Every type declaration is introduced by a type marker, followed by one of six kind words (type value / type resource / type view / type enum / type contract / type intrinsic); the vocabulary + access-control rules below are enforced by the compiler. This doc is the durable rationale — see also GOALS.md §3c.

The type marker#

Every type declaration begins with type, followed by a kind — exactly parallel to fn on every function. This makes declarations greppable and self-describing (grep -n '^type '). The kind words value / resource / view / contract / intrinsic appear only right after type, so they are contextual, not reserved — they stay ordinary identifiers everywhere else (int32 value = 5;, a field or method named resource, etc.). type itself is contextual too: it leads a declaration only where one can begin, and may name a field or a local (C headers name fields type). The exceptions are enum, which is reserved because it predates the type marker, and the qualifiers virtual / abstract / final, which are reserved words.

type value Name     { … }            // owns nothing — copies
type resource Name  { … }            // owns / has identity — moves, RAII-dropped
type view Name      { … }            // borrows a range it doesn't own — a stack-only slice/span
type contract Name for value { … }   // a public-only guarantee (an interface)
type enum Name      { … }            // one of a closed set of variants — a sum type
type intrinsic <int32> implements C { … }   // gives a built-in type a contract's methods

Why reframe#

class / struct / pod (and value-vs-reference) are C/C++ legacy framings that encode the wrong axis. The axis a no-GC / RAII language actually turns on is: does this type own a resource? Rust (Copy vs move), Hylo/Val (value semantics), Swift (~Copyable), and Mojo are all converging here. kama makes ownership the declared nature of a type, so the designer picks the right lever at design time — a "type designer" language that retrains humans and LLMs to think ownership-first.

The kinds#

kind owns? hand-off default polymorphism
value nothing (raw data; may still encapsulate) copy contracts only (external / erased)
resource something, or identity move contracts and internal vtable
view nothing — borrows a range copy (a borrow; stack-only, can't escape) contracts only
contract — (a public-only guarantee, no state) — is the polymorphism / substitutability lever
enum nothing, beyond its variant payloads copy (or move, if a payload owns) contracts, via a tag-dispatched vtable
intrinsic — (declares no new type) — (the primitive's own) how a built-in satisfies a contract

These are the nature nouns. virtual / abstract / final are qualifiers (below), not kinds.

intrinsic is the odd one and belongs here anyway: it is the kind a primitive is. It declares nothing new — it decorates existing built-in types with a contract's methods, one block covering a whole set of widths (type intrinsic <int8, int16, int32, int64> implements Hashable { … }). You write one only to give a built-in a conformance; you name it constantly, because every contract's mandatory for clause lists the kinds allowed to implement it, and primitives are spelled intrinsic there: type contract Hashable for value, resource, enum, intrinsic. See SPEC.md § type intrinsic.

value — owns nothing, copied#

A value is defined by its bits: copying it is a memcpy, and it owns nothing to free. It is the stricter cousin of a "value type" — where a C# struct can smuggle a heap reference (copying it shares that object), a kama value owns nothing, so its copy has no hidden shared ownership.

import { std::math::sqrt };

type value Vec2 {
    public float32 x;       // fields choose visibility per field
    public float32 y;
    public ctor make(float32 x, float32 y) { this.x = x; this.y = y; }
    public const fn float32 length() { float32 q = this.x*this.x + this.y*this.y; return sqrt(x: q); }
}

type value Rect {
    float32 x; float32 y; float32 w; float32 h;   // private (default) — guards its own invariant
    public ctor make(float32 x, float32 y, float32 w, float32 h) { this.x = x; this.y = y; this.w = w; this.h = h; }
    public const fn bool contains(Vec2 p) {
        return p.x >= this.x && p.x < this.x + this.w && p.y >= this.y && p.y < this.y + this.h;
    }
}                                          // still copies freely — it owns nothing

resource — owns something (or has identity), moved#

A resource is moved by default and RAII-dropped. It becomes destructible by declaring a ~dtor or by owning a resource member (transitively) — you rarely hand-write a dtor; you compose owning members (Owned/Shared/Weak/collections).

import { std::collections::DynamicArray };

type resource Buffer {
    DynamicArray<uint8> data;                      // owned → Buffer is a resource; fields stay private
    public ctor empty() { this.data = DynamicArray.empty(); }
    public const fn isize size() { return this.data.length(); }
}

type resource Token { }   // owns nothing, but move-only by *identity* — a capability / linear token

view — borrows a range it doesn't own, stack-only#

A view is a non-owning, second-class borrow of a contiguous run of memory — a slice / span. The flagship is the stdlib pair View<T> ({ UnsafePtr<T> data; isize len }) and its read-only half ConstView<T> ({ UnsafeConstPtr<T> data; isize len }), but the kind is general: an engine can declare its own type view StridedView<T>, type view Grid2D<T>, type view EcsQuery { ref World w; … }. It is kama's answer to a safe span without a borrow checker — the same shape as C# ref struct (Span<T>, ReadOnlySpan<T>, Utf8JsonDeserializer).

type view View<T> {                               // a slice/span over a buffer it borrows
    UnsafePtr<T> data; isize len;                 // fields are private-only (the raw UnsafePtr must not leak)
    unsafe ctor over(UnsafePtr<T> at, isize count) { this.data = at; this.len = count; }   // always private
    public const fn isize length() { return this.len; }
    public unsafe ref T operator[](isize i) { /* bounds-checked */ return this.data[i]; }
}

DynamicArray<float32> verts = …;
uploadToGpu(window: verts.slice(from: 2, count: 6));   // zero copy, no ownership transfer — a read-only `ConstView`

A view's constructor is always private: a view is handed out by the container that owns the buffer (viewMut(), slice(...)), never built from an arbitrary pointer at a call site.

contract — a public-only guarantee#

"Interface" is overloaded (the public surface of any type vs the abstract type). A contract is the abstract thing: a public-only guarantee a type promises to satisfy. A type's public members are just "its API."

type contract Drawable for value, resource { fn void draw(); }
type contract Animated for value, resource implements Drawable { fn void step(float32 dt); }   // refines: requires Drawable + more

enum — one of a closed set of variants#

An enum is a sum type: a value is exactly one of its variants, and a variant may carry named fields. match is the only way to take one apart, and it is exhaustive.

type enum Shape implements Error {
    Circle(float64 radius), Rect(float64 w, float64 h), Empty;
    public const fn string message() { return "a shape"; }
}

intrinsic — how a built-in joins the model#

int32, float64, bool, char and string are built in; intrinsic is the kind that lets kama code give them contracts, so no conformance is hard-coded in the compiler. It declares no type and no state — only methods — and one block covers a whole set of targets:

type intrinsic <int8, int16, int32, int64, uint8, uint16, uint32, uint64, isize, usize> implements Hashable {
    public const fn uint64 hash() { return cast<uint64>(this); }     // the prelude's own
}

Polymorphism: substitutability, not reuse#

Using polymorphism/inheritance for DRY is the anti-pattern. The goal of subtyping is substitutability ("is-a", Liskov — swap an implementation behind a guarantee); DRY is a side effect. Inheritance is overused because it bundles two goals. kama unbundles them:

A contract alone gives no reuse (it's a pure guarantee); reuse comes from a generic — optionally bounded by a contract (fn sort<T: Comparable<T>>(...)). Together, generics + contracts give value types everything inheritance did — reuse and is-a — without inheritance's coupling.

Two dispatch mechanisms (differ by where the vtable lives)#

Both value and resource satisfy contracts. Storing a contract over a resource you keep needs an owning handle (Shared<Drawable>); over a value it's a second-class borrow (can't escape/store). Ownership introduces the destructor — polymorphism does not (except the virtual dtor for owned hierarchies).

The lever cheat-sheet#

Access control + extensibility#

Default visibility everywhere: private. Full per-member matrix (default in bold; protected† = only inside a virtual/abstract resource):

member value resource view contract
field private, public private only private only — (no fields)
method (non-virtual) private, public private, public, protected† private, public public-only, no body
operator private, public private, public private, public public-only (if required)
static method private, public private, public, protected† private, public —
constructor private, public private, public, protected† private only may be required (public)
destructor ⛔ (→ resource) ✅ 0..1 (RAII-called) ⛔ (→ resource) ⛔
virtual / abstract ⛔ ✅ protected-only ⛔ (sealed) ⛔ (it is the abstraction)
final ⛔ (already sealed) ✅ (seal an override / a subclass branch) ⛔ (already sealed) ⛔

An enum's methods and constructors follow the value column (a variant's payload fields are its data, not members). An intrinsic block declares methods only.

Eight rules make the grid memorable:

  1. Default = private everywhere.
  2. protected ⟺ an extensible resource (virtual/abstract). It's meaningless without a subclass, so it's an error on a value, a view, a sealed resource, or a contract.
  3. overridable ⟹ protected. virtual/abstract methods are protected-only — private can't be meaningfully overridden, and public-overridable is bad design. The public polymorphic face is a contract (or a public non-virtual method). This bakes in NVI (Non-Virtual Interface).
  4. public fields ⟺ value; a resource (ownership encapsulated) and a view (its borrowed raw UnsafePtr must not leak) keep fields private.
  5. ~dtor ⟺ resource (forbidden on a value or a view — neither owns anything to free).
  6. virtual/abstract/final ⟺ resource (values and views are sealed → use contracts; a contract already is the abstraction).
  7. contract = all-public signatures (methods, and optionally a ctor/static fn requirement), no fields, no bodies, no dtor; may refine other contracts. A friend grant on a contract is an error — every member is already public.
  8. view codegens like a value (inline, bit-copied, sealed, no ~dtor) but adds two guards: private-only fields and the second-class borrow rule — a parameter/local/return-that-borrows- this, never a field, collection element, or enum payload (see the view section above).

Extensibility qualifiers#

The NVI consequence#

Because public-virtual is banned, a contract method that must vary per subclass is satisfied by a public non-virtual method that delegates to a protected virtual/abstract customization point:

type contract Shape for resource { fn float32 area(); }

type abstract(maxDepth: 1) resource Polygon implements Shape {
    public ctor make() { }                                    // a subclass installs it as its base
    public fn float32 area() { return this.computeArea(); }   // public, non-virtual: the stable face
    protected abstract fn float32 computeArea();              // the protected customization point
    ~Polygon() { }
}

Callers use the public/contract face; subclasses override the protected virtual. Rare in practice — most polymorphism is contracts + monomorphized generics; virtual inheritance is only for shared-impl.

Hand-off marker rule — every kind is movable; the default is declared, a marker overrides#

There is no !Movable and no "ambiguous → must annotate": every owning kind is movable, each kind has a natural bare hand-off, and a give/copy marker overrides it. A hand-off is a named value handed off in an initializer, assignment, argument, or return; a fresh new/ctor/call result never takes a marker.

A bare hand-off is never a silent copy of a resource (the double-drop hole is closed in every case) and never a silent move of a Shared you meant to share (a Shared's bare default is retain). This is compile-time move tracking with zero runtime overhead by construction — a value moved on some-but-not-all paths that is still live at scope exit is rejected, not tracked with a runtime drop-flag (Optional<T> is the explicit escape hatch for genuinely-conditional ownership). The full give/copy behavior matrix (every cell backed by a fixture) is in SPEC.md.

Edit this page on GitHub