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
- A "plain-old-data" type is just a
valuewhose fields are allpublic. Encapsulation (public vs private fields) is a per-field choice, not a separate kind;memcpysemantics hold either way. - Checked intent: a
valuethat (transitively) owns a resource is a compile error ("declareresource"). Likeoverride— derivable, but a checked assertion that catches a design/field disagreement, and it closes a latent hole (avalueholding anOwned→ double-free). - A
valueis sealed and has no destructor — declaring~dtoron a value is an error whose message is the lesson: "a value owns nothing — a~dtormakes it aresource."
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
resourcefields are private-only — ownership (owned handles, invariants) stays encapsulated; expose behavior through methods.- An empty
resource(Token) is valid: "move" is decoupled from "has-a-dtor." It's the linear / capability / witness pattern. (A genuinely-unused one is caught by the general dead-code lint, not a special rule.) - A non-owning member does not make you a resource: a raw
UnsafePtr<T>/UnsafeConstPtr<T>(unsafe borrow) or a borrowedcontractvalue confers no ownership → still avalue.
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.
- Codegens like a
value— inline, bitwise-copied, no dtor. But it is not a transparent data-bag: it has an invariant (a borrowedUnsafePtr<T>that must not leak,ptr/lenkept consistent), so — like aresource— its fields are private-only. - Owns nothing. A
viewmay not declare a~dtorand may not have an owning/resource field (that would make it try to free memory it doesn't own) — the compiler rejects both. - Second-class borrow (the escape rule). Exactly like a
contractvalue, aviewmay be a parameter or a local but not a field, a collection element, or anenumpayload — and it may be returned only when it borrowsthisor aref/view parameter (so the buffer outlives the call, the same structural rule as aref T/const ref Tplace-return). Aviewover a local can't be returned — it would dangle. To hand back data, own it (copy into aDynamicArray). Read-only intent at a call site is aConstView<T>parameter, which aView<T>narrows to implicitly. No lifetime tracking is needed — the escape check is purely structural.
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
- All members are public (a contract is public) — no visibility modifiers, no fields, no bodies
(no default methods, v1), no dtor. Besides methods a contract may require a
ctoror astatic fn, which is how a bound gets to construct rather than only to call. - Explicit satisfaction only (a type declares it satisfies a contract) — never structural/implicit.
- Granularity: keep contracts small; an API requires the narrowest one it needs. Contracts refine each other (capability layering) without class inheritance.
- A contract that mentions its implementing type pins it:
type contract Comparable<T is This>, writtenimplements Comparable<This>at the conformer.
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"; }
}
- It owns nothing of its own — only what its payloads own. So it copies like a
value, unless a variant's payload holds a resource, in which case it moves like one. - A variant is not a type:
Rectis a name inShape's scope, reached asShape::Rect(w: …, h: …). - It takes methods, contracts, named
ctors andfriendgrants like any other kind, and is sealed.Optional<T>andResult<T, E>are ordinary generic enums from the prelude.
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
}
thisis the primitive value;Thisis each target in turn.- It is how
int32isHashable,Comparable<This>,SendableandFormattable— inprelude/global.kama, where go-to-definition lands.
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:
- reuse / DRY → generics (monomorphized, write-once-stamped-per-type, zero cost for values)
and composition; shared implementation up an owned hierarchy →
virtual/abstract resource. - substitutability / swap → contracts.
- (ownership →
value/resource, orthogonal to both.)
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)#
- Inheritance (
virtual/abstract): the vtable pointer is embedded in the object; polymorphic instances are used only through owned handles (Owned/Shared<Base>) and need a virtual destructor. Resource-world by construction — avaluecan't bevirtual(an embedded vtable breaks free copy / invites slicing). - Contract (external / erased): the value's layout is unchanged. Dispatch is either
- a generic bound (
T: Drawable) → monomorphized: direct, inlinable calls, no vtable, no dtor, zero runtime cost (type set fixed at compile time; cost = code size + compile time); or - a runtime contract value → a fat pointer (data + witness vtable, 2 words) + one indirect call, no inlining — buys runtime swappability.
- a generic bound (
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#
- Owns something / needs identity? →
resource. Else →value. - A fixed set of alternatives, some carrying data? → an
enum(andmatchon it). - A borrowed window onto someone else's buffer? → a
view. - A built-in type needs a contract? → an
intrinsicblock. - Reuse an algorithm across types? → a generic (zero-cost; bound it with a contract if it needs behavior). Don't inherit for reuse.
- Need to swap implementations? → a contract (works on
valuetoo, and cheaper — don't reach forresourceto get polymorphism; that's the contract's job).- interchangeable at compile time → contract as a bound (monomorphized, zero runtime cost).
- interchangeable at runtime (plugins / heterogeneous / DI) → a contract value (indirect).
- Default: no contract (concrete-first; add abstraction only when a 2nd impl / real decoupling appears — YAGNI / GOALS §4).
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:
- Default = private everywhere.
protected⟺ an extensibleresource(virtual/abstract). It's meaningless without a subclass, so it's an error on avalue, aview, a sealedresource, or acontract.- overridable ⟹ protected.
virtual/abstractmethods 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). - public fields ⟺
value; aresource(ownership encapsulated) and aview(its borrowed rawUnsafePtrmust not leak) keep fields private. ~dtor⟺resource(forbidden on avalueor aview— neither owns anything to free).virtual/abstract/final⟺resource(values and views are sealed → use contracts; a contract already is the abstraction).contract= all-public signatures (methods, and optionally actor/static fnrequirement), no fields, no bodies, no dtor; may refine other contracts. Afriendgrant on a contract is an error — every member is already public.viewcodegens like avalue(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, orenumpayload (see theviewsection above).
Extensibility qualifiers#
- plain
resource= sealed (the default). type virtual(maxDepth: N) resource= an extensible base (vtable + shared implementation); subclassesextendsit andoverride.maxDepthis the hierarchy's declared depth budget — how many levels may derive below this base — so a hierarchy cannot grow deep by accident.type abstract(maxDepth: N) resource= extensible + non-instantiable; anabstractmethod (no body) forces the enclosing type to beabstract.final= seal avirtualmethod (no furtheroverride) or a subclass branch. Redundant on a plain resource (already sealed).
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.
value→ copy (a value's "move" is a copy; the source stays valid).view→ copy (a bit-copy of the borrow —{ptr, len}— so the source stays valid, exactly like avalue). A view owns nothing, sogiveis meaningless andcopyis redundant; and because a view is a second-class borrow, no hand-off can outlive the buffer it borrows (the escape rule, above).resourcewithout a copy contract (move-only) → move on a bare hand-off (the source is consumed);giveis optional emphasis;copyis an error — nothing to copy with — until it opts in.resourcewith a copy contract → it must declare its bare default at opt-in:implements Copyable<This>(bare: give)(bare moves) orCopyable<This>(bare: copy)(bare deep-copies via its publicctor copy(ref This source)). Without(bare: …)it is a compile error.give xmoves,copy xdeep-copies — a marker always overrides the declared default.Shared/Weak(shared ownership,implements Copyable<This>(bare: copy)) → a bare hand-off retains (refcount++);copyis the explicit retain;givemoves the handle — the ref transfers and the source is consumed (how aSharedreturns from a factory without a spurious retain/drop).contract→ no hand-off of its own. Acontractholds no state and isn't instantiable, so a contract-typed binding is always a concrete implementor (avalue/resource) or a smart pointer over one — the hand-off follows that type's rule. In a generic<T: SomeContract>, aThand-off is whateverT's kind dictates (avalueTcopies, aresourceTmoves).
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.