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. ~dtorresource (forbidden on a value or a view — neither owns anything to free).
  6. virtual/abstract/finalresource (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