Skip to content
You are reading v0.1, which is not the current release. Go to v0.4.

Declarations

This page lists the declaration forms accepted at module scope. Local bindings and expression forms are covered in Expressions.

module app::users;

A module path uses :: between segments. The module declaration belongs at the beginning of the source file.

Selected names:

import std::core::{List, Option, print};

Aliased name:

import app::storage::{User as StoredUser};

Glob import:

import app::prelude::*;

Grouped imports allow a trailing comma.

pub makes a declaration visible outside its declaring module:

pub fn parse(input: String) -> Value {
// ...
}

Without pub, a top-level declaration is module-private. Current Khora uses pub; export is not the public-visibility keyword.

const NAME = "khora";
pub const DEFAULT_PORT: Int = 8080;

General form:

pub? const Pattern (: Type)? = Expr ;

A module-level binding is a const, not a let. const mut is invalid; Khora has no mutable global binding.

Reach for const for a genuine constant — a limit, a protocol value, a fixed default — and for let for a value computed while a function runs.

Alias or structural definition:

pub type UserId = Int;
pub type User = {
id: UserId,
name: String,
};

Variant type:

pub type Result<A, E> =
| Ok(value: A)
| Err(error: E);

Opaque declaration:

pub type Handle;

General form:

derive(...)? pub? type Name<TypeParams>? (= TypeDefinition)? ;

A derive clause appears immediately before the type it applies to:

derive(Eq, Ord, Show, Hash)
pub type Point = {
x: Int,
y: Int,
};

The syntax accepts a comma-separated list of trait names and an optional trailing comma. The compiler-supported derivable traits are Eq, Ord, Show, Hash, Decode and Encode.

The trait must be in scope, so derive(Show) needs Show imported from std::core. Derive where the implementation follows from the fields, and write an impl where the behaviour is a domain decision rather than a structural consequence of the data.

derive(Decode) reads the /// above the type and above each record field into the schema’s descriptions, so a JSON Schema rendered from the type says what the comment says. The comment is the description; there is nothing to write a second time.

A field’s type decides whether the derive is available at all, and a missing impl is sometimes the point:

  • List<A> has Show and Eq when A does, so a record holding a list derives both, and Decode/Encode from std::schema on the same terms.
  • Redacted<A> has Show — it prints <redacted> — and Decode, and deliberately no Encode. A record holding a secret stays printable and readable, and refuses to serialise, so the build stops rather than the payload leaking. It has no Eq either: comparing two secrets byte by byte is how a timing side channel gets written by somebody who was not writing one.

Definition:

pub fn add(left: Int, right: Int) -> Int {
left + right
}

Generic and effectful definition:

pub fn load<A>(id: Id) -> A
with { store: Store }
raises StoreError
{
store.load(id)!
}

Signature without a body:

fn intrinsic(value: Int) -> Int;

General form:

pub? fn Name<TypeParams>? (Params) (-> Type)? EffectClause* (Block | ;)

There is no = between a function signature and its block body, and a function definition has no semicolon after the block.

The clauses are read in order. pub fn load<A>(id: Id) -> A with { store: Store } raises StoreError says: given an Id it produces an A, it requires a capability named store implementing Store, and it may fail with StoreError. The two rows are independent dimensions of the type — see Capabilities and Failures.

A public signature states both. A private helper can usually let the compiler infer them, and inference is why a row variable appears in a signature nobody wrote one into.

Declare a C symbol with contextual extern before fn:

extern fn strlen(ptr: Ptr) -> U64;

A public external function can form part of a generated C-compatible library surface:

pub extern fn khora_add(left: Int, right: Int) -> Int;

See FFI for ABI rules and supported boundary types.

pub effect Store {
load: Id -> User raises StoreError,
save: User -> () raises StoreError,
}

General form:

pub? effect Name<TypeParams>? {
operation: FunctionType,
...
}

Effect members are named function types separated by commas.

pub context Production {
scope: Scope::root(),
db: postgres_db()!,
store: sql_store(),
}

General form:

pub? context Name {
label: Expr,
...
}

Bindings are evaluated in order; a later binding may use capabilities introduced above it.

pub trait Named {
fn name(self) -> String;
}

With supertraits and an associated type:

pub trait Iterator: Show {
type Item;
fn next(self) -> Step<Self, Self::Item>;
}

Trait functions may be signatures or have default bodies.

Trait implementation:

impl Named for User {
fn name(self) -> String {
self.name
}
}

Inherent implementation:

impl User {
pub fn display_name(self) -> String {
self.name
}
}

Generic implementations may put type parameters after impl:

impl<A: Show> Show for Box<A> {
fn show(self) -> String {
self.value.show()
}
}

Inside a trait:

type Item;
type Item: Show;

Inside an implementation:

type Item = User;
test "addition works" {
assert(add(20, 22) == 42);
}

General form:

test "name" Block
bench "parse payload" {
parse_payload(fixture)!;
}

General form:

bench "name" Block

test, bench, context, extern, and derive are contextual keywords in their declaration positions rather than globally reserved identifiers.

Khora 0.1.0built frombd964738afb6on 2026-10-17 20:41ZVersioningSecurityContributingReleasesSource