Documentation

kama keyword reference

Every type declaration begins with the type marker followed by one of six kinds: type value (owns nothing, copies), type resource (owns/identity, move-only, RAII-dropped), type view (a non-owning, stack-only borrow — a slice/span; C# ref struct), type enum (a sum type), type contract (a public-only guarantee — an interface), or type intrinsic (gives a built-in type a contract's methods). The kind words value/resource/view/contract/intrinsic are contextual, not reserved — they mean a kind only right after type, and stay ordinary identifiers everywhere else (int32 value = 5;, View v = a.viewMut();); enum is reserved. See the type-model doc and the rows below.

Legend for the status column:

Status table#

Keyword(s) Status Notes
bool int8/16/32/64 uint8/16/32/64 float32 float64 isize usize void string core types. isize/usize (ptrdiff_t/size_t) are platform-varying, as are clong/culong (C's long/unsigned long, 4 bytes on Windows and 8 elsewhere — for an extern mirroring a C long); cchar is C's char as a raw-pointer element only (UnsafeConstPtr<cchar> = const char*, what string.cstr() returns), never a value — isize is the size type: every length()/count()/index is one, and usize is reserved for the C ABI (sizeof, an allocation, an extern fn). Bare int and double are NOT types — write int32/isize and float64. string is a fat value: a borrowed literal/view (no alloc) or a heap-owned RAII string (freed on drop); UTF-8 bytes throughout (see SPEC §Strings)
char a Unicode scalar value (codepoint), a distinct primitive backed by uint32 (NOT a numeric type — no silent int mixing). Literals 'a' / '\n' / '\u{…}'; equality + ordering; char↔int32 via cast. Yielded by .chars()
type the type-declaration marker — every type is type <kind> Name { … } (parallel to fn). The kind is value/resource/view/enum/contract/intrinsic (+ the virtual/abstract/final qualifiers after type). Greppable (grep '^type '). Contextual: it leads a declaration only where one can begin, and may name a field, local or parameter elsewhere (C headers call fields type)
value resource view contract intrinsic contextual, not reserved. They name a kind only right after type; everywhere else they are ordinary identifiers (int32 value = 5;, a field/method named resource). type value = owns nothing, copies, sealed, fields private-or-public per field, no dtor. type resource = owns/identity, move-only, RAII-dropped, fields private-only. type view = a non-owning stack-only borrow (a slice/span — the flagship is View<T>; C# ref struct): codegens like a value (inline, copies, no dtor) but the escape check forbids it as a field, collection element, or an escaping return; owns nothing (no ~dtor, no owning fields), fields private-only. type contract = public-only guarantee (an interface): methods only, no bodies/fields/ctor/dtor; satisfied via implements; may refine another via implements (type contract A for value, resource implements B). A contract carries a mandatory for clause naming which kinds may implement it — a comma list of any of value / resource / view / enum / intrinsic (not contract: contract-to-contract is refinement). type intrinsic <int32, int64> implements C { … } = one block of methods that gives a set of built-in types a contract — how the prelude makes int32 Hashable in kama rather than in the compiler; it declares no type and no state
enum a type enum is a plain set or a tagged union (variants carry payloads; may be generic), consumed by match. Like every kind it takes an implements clause and carries the methods that satisfy it, after a ; that separates them from the variants. enum is the one kind word that is a reserved keyword rather than a contextual one
match case match is the one construct for branching on an enum — plain enums, tagged unions, and Optional/Result alike. Value-producing (statement or expression position), compile-time exhaustive, with a _ wildcard; case heads each arm and binds payloads (case Some(value: v): …). The subject may be a variable, method call, or free-function call. Arbitrary-integer branching uses if/else if — there is no switch
operator operator overloading. Set: arithmetic + - * / %, bitwise & | ^ << >>, unary - ! ~, ++/--. Arity picks the form: 0 params = unary on this (Vec2 operator-()), 1 = binary method (Vec2 operator+(Vec2 rhs), this is the left operand), 2 = binary free/static form (Vec2 operator*(int32 s, Vec2 v) — enables scalar-on-the-left). Lowers to a direct call of the type's op_add function (&lhs, rhs). Type-based dispatch: a type may carry several operator* distinguished by operand type — mat*vec + mat*mat, v*s + s*v — like C++/C#/Rust; only the same symbol AND operand type collide. Chaining (a + b + c, in any position incl. a condition — nested rvalues wrap in a C99 compound-literal array); compound assignment pos += velpos = pos + vel. The six COMPARISON operators are NOT declared here — they come from a contract: ==/!= lower to Equatable.equals, </>/<=/>= to Comparable.compareTo, and declaring operator== & co. is a hard error naming the implements form. (Operators a generic bound must NAME are contracts; the rest are concrete-type ergonomics.) Equality stays explicit — no auto structural equality — but @generate(Equatable, Hashable) synthesizes the memberwise walk on request. true/false conversion operators out of scope. In a contract, an operator is a bound for generic math
extends implements this base Base base.m() reaches an inherited member non-virtually (named args; same visibility rules as this.). this.base = Base.<ctor>(…); is a derived ctor's mandatory FIRST statement — it installs the base part, built by the base's own ctor. Base names the base type contextually, as This names the enclosing one (a real type called Base wins); this.base may only be ASSIGNED
ref out Two different promises, both passed by pointer. ref T x is a read-write borrow of an already-live value; its call-site marker is optional. out T x is a fill: the callee must assign it on every path before returning and may not read the incoming value, and the call site must say out (divmod(q: out quotient)) — both lower to the same T*, so without the marker neither a reader nor the caller's definite-assignment analysis could tell a borrow from a fill. out is what lets a slot be filled by a callee. "Every path" is a real flow merge: both arms of an if/else count, a lone if does not
slot a declared HOLEslot T x; is storage with no value in it yet: illegal to read until definitely assigned, and no destructor is emitted while it stays unassigned, so "drop only if live" is proven statically rather than checked at runtime. A local with no initializer and no slot is an error; so is slot with an initializer. It does not run the type's default ctor (spell T x = T.empty();). Filled by a whole assignment, a field write, an out argument, a method call on it, or addr(of: x). Assigned on only some paths → it drops (the storage is always valid). Not Optional<T>: no tag, no drop, nothing at runtime. See SPEC.md Uninitialized storage
file the FILE GATEfile @compileFor(FLAG); as a unit's FIRST line (ahead of import) gates the WHOLE file the way @compileFor gates one declaration: a build the flags exclude never admits the file at all, so a native-only source costs a wasm build nothing. Excluded, not deactivated — its declarations, its export, its imports and its contribution to its module do not exist for that build — but it IS still parsed, so a syntax error in it is an error on every target. Takes @compileFor and nothing else. A file the CLI NAMES and whose gate excludes it is an error; one the build COLLECTED is skipped silently. Contextual: everywhere but that first position file is an ordinary name (File file = …). See SPEC.md Conditional compilation
This the self-type (contextual, not a lexer keyword). Inside a type's own body it names that type — fn This clone(), implements Comparable<This> — and needs no declaration, because nothing is erased there. A contract may not name This in a signature; it declares the self-type as a pinned type parameter instead (type contract Comparable<T is This>) and writes T. The reason is erasure: a bound monomorphizes and can substitute This, a contract value has thrown the type away, so a vtable slot would have to bind it to the contract while the function behind the slot bound the implementing type. A pinned parameter is a real type argument that resolves the same on both sides. Distinct from the lowercase this value. Outside a type/contract it's a clean error
generics <T> / bounds <K: A + B> / turbofish f::<T>() monomorphized (zero-cost, no boxing). Generic functions (type args inferred; a return-only generic uses turbofish f::<int32>() — the :: before < is unambiguous) and types (type value Pair<A,B>, generic resource), multi-param + nested (Box<Pair<int32,int32>> — the >> split means no space). Contract bounds <K: Hashable + Comparable<K>> (+ = AND) → the body may call the contracts' methods on a type-param, lowered to static direct calls; each concrete arg is checked to satisfy its bounds
new the heap operator. Owned<Box> p = new Box.make(...) boxes the element type on the heap; a stack value drops new (Box b = Box.make(...)). Construction is always a named ctor called dot-on-type — the nameless Type(...) / new Type(...) form is a hard error. new into a plain value type is an error. A fallible ctor composes: new File.open(...)Result<Owned<File>, E>. Infallible — panics on OOM (see try new).
ctor the one kind of constructor — a named factory returning the type (or Result<This, E>), implicitly type-associated (no fn, no static). Called dot-on-type: Buffer.make(size: 8) constructs, while :: stays scope resolution (Vec3::dot, Enum::Variant) — so .make( greps for construction and catches nothing else. A generic ctor puts the turbofish on the TYPE (T::<Args>.make(…)). Nothing is constructible by default: no ctor and no of/zero opt-in means the type cannot be built. Every field must be assigned before it returns (or carry a field default T x = …;) — complete-by-delegation when the ctor ends in return Other.make(…). Reuse is an ordinary call: no init hook, no designated/final ctor, no funnel. A self-returning static fn and a class-named ctor are both rejected as disguised constructors. A contract may REQUIRE a ctor (ctor adopt(UnsafePtr<T> raw)), which is how generic code constructs through a type parameter — and why there is no privileged Default contract. deserialize and Copyable's copy are ordinary ctors. See SPEC.md Construction
try new non-panic construction (MCU step 5). Optional<Owned<Box>> b = try new Box.make(...) yields None on OOM instead of panicking — the ONE fallible construction entry (new stays the infallible sugar; there is no tryAllocate). A typed local initializer or a return of an Optional<Owned<T>>; the placement form try new(allocator: a) Box.make(...) draws from a custom allocator. try also prefixes cast (try cast<T>(x), below).
asm inline assembly (MCU step 6a). asm("wfi"); — a statement taking one string literal — lowers to __asm__ __volatile__("…" : : : "memory"). Requires an enclosing unsafe fn (the sole raw-operation seam; outside it is a hard build error). Always volatile (never elided/reordered) and always a full compiler memory barrier — so cpsid i/dsb/dmb order memory correctly by default. Multiple instructions go in one \n-separated string (asm("cpsid i\n\tdsb")). No interpolation; the text is target-specific (portability is the user's, like FFI). Named helpers (wfi(), disable_interrupts()) are an ordinary library built on this primitive. See SPEC.md Inline assembly
virtual override an overridable method is written protected (never public/private), and its type must opt in as a type virtual(maxDepth: N) resource/type abstract(maxDepth: N) resource (extension is a resource concern — a value is sealed); override only re-seats a real base slot
give copy hand-off markers. give = move (invalidates the source); copy = retain (Shared/Weak) or duplicate. Ride a named value (a fresh new/ctor/call result needs none) and work uniformly in initializer, assignment, argument, and return positions. Every owning kind is movable; a bare hand-off follows the kind's default, a marker overrides. Defaults by kind: Owned → move (copy is an error — unique), Shared/Weakretain (bare = refcount++; give moves the handle, copy = explicit retain — implements Copyable<This>(bare: copy)), value/primitive → copy (a value's "move" is a copy), a plain resource (move-only) → move (bare hand-off moves; copy is an error until it opts into Copyable). A collection requires the marker: give = move (buffer), copy = deep copy — element-wise: a bitwise-copyable element copies memberwise, a Copyable-resource element deep-copies via its own copy ctor; a non-Copyable resource element is rejected. Copyable resource: a resource opts into copy nominallyimplements Copyable<This>(bare: …) plus a public copy CTOR ctor copy(ref This source) (the contract declares it ctor copy(ref T source) on Copyable<T is This>; a type's own body still writes This) (a lone copy ctor without the implements does not opt in; it is a ctor because a copy IS a new object, and the source is borrowed); opting in must declare its bare defaultCopyable<This>(bare: give) or Copyable<This>(bare: copy) (an implements Copyable<This> without (bare: …) is a compile error) — then a bare hand-off follows that default, copy x deep-copies via the copy ctor, give x moves. Since copy/give are only markers in expression position, they're contextual — usable as member names (so the opt-in ctor is literally copy). Full behavior matrix (every cell → fixture) in SPEC.md
addr drop panic assert debugAssert the call-site intrinsics — the compiler lowers each at the call because it needs the call site (the source text and file:line, or the operand's storage): addr(of: x) (a raw pointer, unsafe fn only), drop(ptr: p), panic(msg:), assert(cond:, msg:), debugAssert(cond:, msg:) (stripped under --release). Reserved words, legal only in call position, and always bare (global::assert was retired with KR-87). They were floor NAMES until 0.9.425, so a local could take one and a user's fn void assert(…) was accepted and then never called. See FLOOR.md Diagnostics
if else do while for foreach in break continue return full control flow (enum branching is match, not switch)
true false null cast truncate bitcast unsafe extern The three conversion verbs, by what each PRESERVES. cast<T>(x) preserves the value and so TRAPS when the value does not fit T — in every build, a constant one at compile time; truncate<T>(x) preserves the low bits (target narrower or equal, and a provable widening is rejected — there are no bits to drop); bitcast<T>(x) preserves all the bits at the same width (the only one crossing int↔float — IEEE-754 bits, hashing, endianness) and rejects a width mismatch. The fallible form is try cast<T>(x)Optional<T>: None where the plain cast would trap, and like try new it needs a declared Optional<T> destination. truncate is contextual — a keyword only where a conversion can start, so string's truncate(maxBytes:) and a method of that name still work. null is only for UnsafePtr<T> / UnsafeConstPtr<T> at the FFI boundary; unsafe fn is the sole raw-pointer memory seam. unsafe is a MODIFIER on a body-bearing declaration (method, ctor, destructor, operator, free fn) — C#'s meaning, not Rust's: it marks the BODY, so calling one is unrestricted and public unsafe fn is ordinary. Rejected where no body exists (a type, a field, an abstract method, a contract member)
import as export the module system. One import { … }; block and one export { … }; block per file, both at the top. Every import entry names a SYMBOL — import { a::b::X, a::b::Y as Z, W }; — so a::b::X is symbol X of module a::b, as renames, and a scope-less W names a symbol of this file's OWN module. There is no whole-module import and no glob, and a module path is written only here (and in a friend grant) — every use is the bare name the import binds (KR-87). A module is a FOLDER, named by the project's kama.json; resolution goes through the manifest, then $KAMA_PATH, then the bundled stdlib (std/core/global are reserved roots; core is the runtime capabilities, embedded in the compiler). Visibility is per FILE: a name leaves its file only by being listed in that file's export, and reaches another file only by being listed in that file's importexport offers, import accepts. Declarations carry no visibility modifier (so type/fn syntax stays uniform). Two entries binding the same bare name is an error (use as). Replaces using (retired)
static static methods AND module-level statics. (1) static fn has no implicit self and is called at type level: Vec2::dot(left:, right:). static + virtual/override/abstract is a contradiction (no vtable slot); a this/bare-field reference in a static body is a clean error. (2) static T name = const; at module scope (MCU step 1) — a module-level variable with deterministic zero/const init at reset. Per-isolate by construction (_Thread_local on native + wasm-pthreads, plain static on a single-core embedded target), so it's race-free under the threading model — cross-isolate sharing stays on the Atomic<T> seam. v1 restricts to value / UnsafePtr / InlineArray (owns nothing, no teardown) and compile-time-const initializers (literals / sizeof / const arithmetic; omitted = zero-init). This is firmware's home for ISR↔main flags, peripheral handles, and ring buffers — see SPEC.md Module statics
public private protected Members are private by default; public/protected set it explicitly. protected = owner-or-subclass and is meaningful only inside an extensible resource (type virtual/abstract resource) — an error on a value, a plain resource, or a contract. Field visibility is per field on a type value (public field = a plain-old-data struct); a type resource keeps fields private; a type contract has no fields
friend granular, owner-granted grants: friend <accessor>[members]; (or [...] = all privates). The accessor is a type, a free function, or a Type::method (a named ctor included), named bare or by qualified path across files and modules with no import; it may then reach the named private members. Granting an unknown or public member is an error; a grant into a module absent from the program is inert
abstract type abstract(maxDepth: N) resource is non-instantiable (new rejected; also when a subclass leaves an inherited pure method un-overridden) — though its ctor IS callable in a derived type's base install; methods are protected abstract; the type must declare ≥1 overridable member and a ctor. A qualifier after type, on a resource only
final type final resource is a sealed leaf (cannot be extends-ed) — it is a depth budget of 0, and the only spelling for one; final on a method seals its slot (no subclass override); only a type virtual/abstract resource may be extended at all (a value and a plain resource are already sealed)
const a const binding is deeply immutable — no reassign, no write through it, no ++/--, no give out of it (a move mutates its source), and addr(of:) into it hands back a read-only UnsafeConstPtr<T>, never a writable pointer. const fn is non-mutating and the only kind callable on a const receiver; it may not return ref T (a place out of a const method launders const — the stdlib's get/getRef, iterator/iterMut split is the answer), and it is ABI-neutral (the emitted C is identical). Params take const T / const ref T (read-only borrow); a const field is write-once (constructor only); const UnsafePtr<T> lowers to const T* for const-correct FFI (a const binding; the read-only pointer type is UnsafeConstPtr<T>, T const*, which stores nothing and does not convert to UnsafePtr<T>). A contract member may be const fn / take const ref, and an implementation must honor both — unlike unsafe, which is rejected on a member because it marks a BODY while const constrains the CALLER's receiver; an override may not drop it either. Not available on a free function (no receiver), ctor, destructor, or operator. A const local whose initializer folds is opportunistically usable in a compile-time position (an array size) — comptime is the explicit, guaranteed form. Full rules: SPEC.md
comptime a named compile-time constant (const-eval 6b-2) — the evaluation-time axis (const=immutable, static=associated storage, comptime=computed at compile time; comptime implies immutable + associated). Three scopes, one keyword: local/block comptime T N = <expr>; (explicit, errors at the decl if it can't fold — vs an opportunistic plain const), module comptime T NAME = <expr>; (a shared constant; no runtime init point, so folding is required), and type comptime T NAME = <expr>; read as Type::NAME (member-visibility-controlled, public/private like a field). Initializer must fold (a literal / sizeof / alignof / const arithmetic / another comptime). Lowers to a real static const symbol (addressable, @section/flash-placeable) and cross-constant refs bake to literals — so there is no C static-init-order dependency. Drives comptime sizes/fills (InlineArray<T>#(NAME)). It is also the compile-time PARAMETER marker in a generic parameter list — fn int32 shifted(int32 x) comptime(int32 S), type value InlineArray<T> comptime(int32 N) — supplied by its own trailing #(...) group (shifted(x: 2)#(3)) or inferred from an argument's type. That slot was spelled const until 0.9.119; it moved because const already meant immutability in three other places while comptime meant compile-time in every one of its own. comptime fn (const-eval 6b-3) extends this to compile-time functions — the compiler RUNS a bounded, pure function and bakes its scalar/table result into a static const (a CRC/gamma/trig LUT in .rodata/flash); comptime-only (never emitted as C), both top-level and type-associated (Type::name(), member-visibility-controlled, default private), purity + step-budget enforced. See SPEC.md Compile-time constants / Compile-time functions
fn fnptr fn heads every function/method declaration. fnptr declares an explicit, named function-pointer type — zero-cost, non-null, signature-checked; a bare function name or Type::method binds it
hardware the MMIO/ISR qualifier (renamed from C's volatile, which is no longer a keyword). hardware UnsafePtr<T>volatile T* — a memory-mapped register, mirroring const UnsafePtr<T>; on a module static, hardware Tvolatile T (a scalar ISR↔loop flag) and hardware UnsafePtr<T>volatile T* (a peripheral handle). Composes with const (const hardware UnsafePtr<T> or hardware UnsafeConstPtr<T>const volatile T*, a read-only status register). Single-core MMIO/ISR only — NOT a concurrency primitive (cross-isolate sharing is Atomic<T>)
spawn starts an isolate — a real OS thread natively, a Web Worker on wasm, shared-nothing (its own stack, heap and module statics). Two forms: Isolate h = spawn f(args…) yields an owned RAII handle whose drop joins (explicit h.join() is the same thing said out loud); a bare spawn f(args…); is legal only inside a scope block, where it becomes a deferred-join child. Arguments cross under the ordinary ownership rules — a resource is moved with give, a value copies — and the bundle's type must declare implements Sendable, a claim the compiler verifies over every field (a type nobody annotated does not cross): a declaration over a non-atomic shared refcount — a Shared/Weak over a mutable payload — is rejected, naming the offending field. A view and a bare contract value cannot be fields at all, so they never reach a bundle; a raw UnsafePtr does cross — that is the unsafe seam, and joining the child is what orders the write. See SPEC.md Concurrency
scope a structured-concurrency blockscope { … }. Every bare spawn inside it is joined at the closing brace, before any local destructor runs: RAII applied to tasks, so there are no orphans and a child may safely borrow from the enclosing scope (it is guaranteed alive until the join). Block-bodied
borrow the lexical window a view lives inborrow h.mint() as v, k.mint() as w { … }. A view LOCAL must root in one (or in a by-value view parameter): kama answers how long is this view valid with lexical scope rather than a lifetime annotation (GOALS.md §3e declines the machinery) or a programmer promise, so the mint must name its extent and the block IS that extent. The host is the mint call, and the mint is any nullary member of a @viewable contract — the grant names its own member, so borrow m.values() as vals and borrow buf.span() as s work as well as view(). The host place is frozen for the block — not unnameable: reads (a.length(), a[i], foreach (x in a)) stay legal, while growing it, reseating it, giveing it out, or passing it as a non-const ref/out do not. The frozen thing is a place — a base plus its chain of field names — so borrow this.buf.view() as b leaves this.hits fully mutable: two distinct fields cannot overlap in storage. Deriving (v.slice(…)) and passing a view to a callee stay free; storing one is still forbidden. foreach and parallel_for are the same window under a different spelling — they hold a borrowing iterator over their operand for the body and freeze it exactly the same way, naming the ELEMENTS instead of the view. Block-bodied; purely lexical, zero runtime cost. See SPEC.md Views
parallel_for the disjoint-slice data-parallel loopparallel_for (ref T e in coll, workers: N) { … } splits coll into K non-overlapping sub-Views, one per worker isolate, mutates each in place, and joins them all at its own closing brace (a self-joining barrier — no enclosing scope required, but it composes inside one). Safe by disjointness: two workers never touch the same element, so no lock and no borrow checker. ref is mandatory, and so is workers: (cpuCount() for one per core); the input is a View<T> or any contiguous container exposing .viewMut() (a Map has none and is rejected; a read-only ConstView<T> is too)
parallel_spawn one long-lived isolate per elementparallel_spawn (ref Worker w in workers) { w.run(); } starts a worker for each element and joins them all at the closing brace: a fixed pool for work that runs for the life of a subsystem, where parallel_for is for one data-parallel pass. Same disjointness proof. See SPEC.md Worker pools
when conditional conformance on a generic type — implements Copyable<This>(bare: give) when [T: Copyable<T>], or a member gated when [A: default]: the conformance or member exists only for instances whose arguments satisfy the bracket. A free function's bounds go in its type-parameter list instead; it has no "sometimes"
sizeof alignof compile-time size and alignment of a type (sizeof(T), alignof(T)), folding to a constant where the layout is known — the verification half of @align/@packed via comptime assert. Reserved words
default contextual. Names the default-allocator bound in when [A: default] and the default target in a manifest; otherwise an ordinary name
immutable a type qualifier — type immutable value T / type immutable resource T — asserting deep immutability, verified by the compiler: every field, base and variant payload must be a primitive, string, enum, or another deeply-immutable type; a mutable member is a compile error naming it. Its purpose is cross-isolate sharing: a Shared/Weak over a deeply-immutable element is sendable, so isolates read one asset zero-copy, and only that case pays for an atomic refcount. Distinct from const, which constrains one binding and so cannot license sharing. A type qualifier and nothing smaller — on a field or method it is a hard error (it parses because the modifier list is shared, and it was silently dropped until 0.9.144); const is the per-field promise, const fn the per-method one
expose marks a free function for the kama→host boundary: it gets an exported C-ABI symbol (KAMA_EXPORT) a host can resolve — dlsym on a kama build --shared .so/.dylib/.dll, or a .wasm module export. The symbol is the module path joined to the name with _ (game::sim::tickgame_sim_tick; a loose file qualifies by its file name), so modules keep exposed names apart in C too; @linkName fixes one verbatim. kama build writes the host's C header for them (<project>.h beside the output). Signature must be C-ABI-safe (no owned-by-value string/collection/Owned/Shared/Weak; use UnsafePtr<T> or an extern struct). Free functions only, not a member modifier — plus two type forms, type expose value (a C layout kama owns) and type expose enum (C constants kama owns, every value spelled), published by the generated header — the pairs of type extern value and type extern enum, whose C names are the header's own, verbatim. Distinct from export (module visibility) — three boundaries, three words. Full 2.0 expose adds richer wasm module exports + the scripting host.

MCU codegen attributes (@interrupt, @section, @noheap)#

Not keywords — declaration attributes on the existing @name(args) mechanism (previously serialization-only), extended in MCU step 4 to functions and module statics. @interrupt/@section emit a C __attribute__((...)) only on the exact declaration they annotate; @noheap (step 5) emits nothing — it is a checker flag. Un-annotated code is unchanged.

Attribute On Lowers to Rules
@interrupt a function (@interrupt expose fn void h() { … }) __attribute__((interrupt, used)) — the Cortex-M / RISC-V / classic-ARM ISR calling convention; used survives --gc-sections Signature must be void h() (no params, no return); expose required so the handler has an exported symbol the vector table can name — usually fixed by the startup code, so spelled with @linkName("SysTick_Handler"). AVR's @interrupt("VECTOR")ISR(VECTOR) macro is a later step
@section(".name") a module static or a function __attribute__((section(".name"))) one string-literal section name — vector table (.isr_vector), flash const table (.rodata), DMA RAM bank, .ramfunc. The board's linker script owns the actual addresses
@noheap any declaration with a body — a free fn, and equally a method, ctor, destructor or operator (@noheap public fn int32 fill(…) { … }) nothing — a checker flag, not codegen Makes every emitter-visible heap allocation a compile error (new/try new, parallel_for/spawn boxing, Owned<Error> boxing, string interpolation's Formatter). No args. Target-independent — an ISR / game frame-tick / real-time audio callback allocates nothing. Transitive: the body may not call anything that allocates, at any depth, including a local's destructor; GlobalAllocator is the leaf, so a container drawing from it is rejected while the same container over an arena is not. Where the callee is unknowable — a fnptr, or a contract/virtual slot — the call is rejected unless the CONTRACT member declares @noheap, which every implementation is then checked against. The whole-program equivalent is the --no-heap build flag

Symbol-name attribute (@linkName)#

Not a keyword — a declaration attribute. The peer of Rust's #[link_name] / #[export_name], one attribute for both directions of the C boundary; named for the concept rather than the backend, because the 2.0 bytecode VM has no C names.

Attribute On Lowers to Rules
@linkName("symbol") an extern fn (the C symbol it binds) or an expose fn (the symbol it exports) the symbol at every call site / as the definition's name — no __attribute__ exactly one string literal; a C symbol (letters, digits, _; not a C keyword — a kama keyword is fine, that is the point); one C symbol has ONE kama binding per program; exported symbols stay unique. On an expose fn it is the one override of the module-qualified symbol. Rejected on an ordinary fn, a member, a static, a fnptr or an extern "<h>"; — none has a symbol to rename. See SPEC.md FFI — calling C and Exposing to a host

Layout-control attributes (@align, @packed)#

The same passthrough mechanism applied to a type rather than a declaration, and the companion to layout verification (sizeof/alignof folding + comptime assert), which shipped first because asserting a layout is what makes stating one safe. kama does not own layout — it emits C and the C compiler lays the struct out — so these state a constraint and the assertions verify what the toolchain actually did, rather than kama keeping a second model that could disagree per target.

Attribute On Lowers to Rules
@align(N) a type with a struct (@align(16) type value Vec4 { … }) __attribute__((aligned(N))) on the emitted struct One literal number, a power of two from 1 to 4096. Held to a power of two rather than passed through because gcc/clang round a non-power-of-two up rather than refusing it, so @align(3) would compile and quietly mean 4
@packed a type with a struct (@packed type value Reg { … }) __attribute__((packed)) on the emitted struct No arguments — it removes padding, it does not set a width. A wire struct or an MMIO register block

Both are refused on an enum (a payload-less one lowers to an integer, a tagged one to a tag plus a per-variant union that an outer packed would not reach — an enum states its layout with type enum E : IntType) and on a declaration (@align(64) static …), so there is one way to align an object rather than two: put it on the type and declare the static with that type. Rust draws the same line — #[repr(align(N))] is types-only. A type may carry both, and either may sit beside @generate(...).

Conditional-compilation attribute (@compileFor)#

Also on the @name(args) mechanism, but not MCU-specific and not codegen — a build-time keep/drop gate. See SPEC.md Conditional compilation and ROADMAP_DETAIL.md §5.

Attribute On Effect Rules
@compileFor(FLAG…) any top-level declfn, type, enum, module static The decl is kept iff its flag gate is active, else dropped before any collect/emit pass (its symbol never exists; no #ifdef reaches the emitted C). The attribute is stripped from kept decls Flags are derived from the target tripleARCH_<arch>/OS_<os>/ABI_<abi> plus HOSTED and SIMD128 — with DEBUG/RELEASE (from --release), NOHEAP (from --no-heap), a project-declared target's own name, and user flags via --define/--undefine. ⚠️ A built-in target NAME is deliberately not a flag: NATIVE/WASM/EMBEDDED/WINDOWS are undeclared, and in a loose build an undeclared flag is inactive, so the decl is silently dropped. Logic is membership + leading ! + comma-AND (@compileFor(!RELEASE), @compileFor(OS_WINDOWS, DEBUG)) — not an expression language. Build-mode and platform are one primitive; platform = the gate on per-target type impls behind a contract. A kama.json manifest (a user-project file) declares the valid flag universe and makes an undeclared name a hard error

Other attributes#

Each is described where its feature is; this is the index.

Attribute On What it does See
@generate(...) a value, resource or enum derives Serializable, Deserializable, Formattable, Equatable, Hashable, or the of/zero constructors SPEC Derives
@field / @field(name: "…") / @field(id: N) / @skip / @deprecated a field of a serialized type marks what is written, under which name or number; @deprecated is read but never written SPEC Serialization
@serializedGraphEdges a container's members names the members an object graph routes through SPEC Serialization
@viewable a contract its nullary members may mint a view inside a borrow window SPEC Views
@foreignEntry / @callerThread a fnptr type handed to C says whether C calls it back on a thread kama did not create, which decides what it may touch SPEC Foreign entry points
@onPanic(recover: v) a function a region that recovers from a panic by returning v instead of terminating SPEC Recoverable regions
@heap an extern fn declares that the C function allocates, so @noheap and --no-heap see it SPEC No-heap
@globalAllocator one resource implementing GlobalHeap replaces the allocator behind every heap block the program uses SPEC Global allocator

Reserved words#

Every keyword above is implemented, enforced, and exercised by the fixtures in ../tests/.

Every C keyword is reserved in kama tooint, double, long, volatile, goto, union and the rest — because kama's names become C names and a binding called union would not compile. Using one is an error that names the kama spelling where there is one: C's volatile is spelled hardware here (see the hardware row), and cross-isolate sharing is Atomic<T>, never either. SPEC.md C's reserved words are reserved in kama has the list.

(The minimal expose — a free-function C-ABI symbol for the native --shared reload boundary and wasm exports — is implemented. The full 2.0 expose — richer wasm module exports and the scripting host interface — remains future work, but the keyword is live today.)

Edit this page on GitHub