Documentation

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

Every type declaration is introduced by a type marker, followed by one of five kind words (type value / type resource / type view / type enum / type contract); the sixth kind, intrinsic, is the built-ins, which are declared by the language and named in a contract's for list. A type adapter block gives a type a contract outside the type's declaration, from the contract's own module. 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 / adapter appear only right after type (and intrinsic only in a contract's for list), 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 adapter <int32> implements C { … }   // gives a type you don't declare 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 — (the built-ins: numbers, bool, char, string) — (the primitive's own) contracts, through a type adapter

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, and nobody declares one. A built-in gains a contract's methods through a type adapter block, one block covering a whole set of widths (type adapter <int8, int16, int32, int64> implements Hashable { … }). You name the kind 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 adapter.

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 { UnsafePtr<World> world; … } — a view borrows through raw pointers, since a field is never a ref, or through other views it holds: a type that holds a view is a view, so a zero-copy tokenizer is type view Tokens { ConstView<uint8> rest; … }, with a public constructor because it holds no raw pointer, and Optional<ConstView<uint8>> is a view too (SPEC Views compose). 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; }   // private: it holds a raw pointer
    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. (Asking which variant it is — isSome(), isOk() on the prelude's two — takes nothing apart.)

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, and intrinsic is their kind. A type adapter block is how kama code gives 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 adapter <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
}

type adapter — a contract for a type you don't declare#

The same block serves every type its author does not declare: a std type, another package's type, one instance of a generic (DynamicArray<uint8>), or a generic's every instance (Optional, narrowed by when [T: C]). It is written in ONE place, the module that declares the contract, and never over a type that module declares (that type says implements C itself). So a type's conformance lives in its own declaration or in its contract's module, never in a third place:

// app::db declares DbParam, and adapts the model's types to it; app::model never imports the db layer.
type adapter <User> implements DbParam { public const fn int32 code() { return this.id; } }
type adapter <Optional> implements DbParam when [T: DbParam] { … }

Its methods are reached only through the contract, never off the type, so an adapter adds no API to the type it decorates. See SPEC.md § type adapter for the rules and why they are what they are.

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). A type adapter 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 frees ⟺ resource (forbidden on a value, which owns nothing to free; a view may declare one that frees nothing and closes its window).
  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