Skip to content

Standard library

A first standard library lives under lib/std and is pulled in with import. Resolution finds it under GEAS_HOME. The modules are std.math, std.strings, std.collections, std.errors, std.traits, and std.store.

The standard library is written in Geas itself, so its surface is honest about what the language expresses today: operations that would need a primitive the language does not yet have stay out rather than ship broken.

The shared error sums the rest of the standard library leans on. CommonError covers the three failures a small API keeps meeting: a lookup that found nothing, an argument that made no sense, and a source that ran dry.

CommonError is either NotFound or Invalid or Exhausted

It is an ordinary sum, so it rides the E slot of any Result and crosses the ABI as a tagged value like every other sum. Modules with a failure vocabulary of their own, the way std.math has GeasMathError, declare it beside their contract instead.

The integer and float arithmetic contract. Every operation is a pledge on MathOps, so a host signs the contract and fulfills them by name, and the error paths ride the GeasMathError sum:

GeasMathError is either DomainError or Overflowed

The contract carries an epsilon vow, default 0.000001, which feeds approx_eq and can be overridden at sign time.

The integer pledges are overflow checked: abs, sign, min, max, clamp, and pow_int, each returning Result<Int, GeasMathError>. abs reports Overflowed on the one integer whose magnitude does not fit, and pow_int guards every multiply through an overflow check before it runs:

pledge pow_int(base: Int, exp: Int) -> Result<Int, GeasMathError> {
if exp < 0 {
return Err(DomainError)
}
let mut acc = 1
let mut i = 0
while i < exp {
if mul_overflows(acc, base) {
return Err(Overflowed)
}
acc = acc * base
i = i + 1
}
return Ok(acc)
}

The float pair of each pledge carries the _f suffix: abs_f, sign_f, min_f, max_f, clamp_f. approx_eq(a, b) answers equality within the epsilon vow. GeasMathRatio is a placeholder record, a numerator and denominator pair, kept in the surface.

The string contract the current surface can honestly offer. The language gives String exactly two operations, concatenation and equality, and no way to read a length, a byte, or a case table, so the StringOps contract is built from concatenation alone:

  • repeat(s, n) builds n copies of a string, Err(Invalid) on a negative count.
  • join(a, b, sep) glues two strings around a separator.
  • wrap(s, prefix, suffix) parenthesizes a string between a prefix and a suffix.

All three return Result<String, CommonError>. The classics that need more than concatenation stay out rather than ship broken: pad needs the string’s width, upper and lower need case mapping, trim and split need byte access. Each lands when the surface grows the primitive it stands on. GeasStrSpan is a placeholder record kept in the surface.

The list contract, ListOps, concrete over List<Int> since user declarations take no type parameters yet. The surface is what a list the language can walk but never grow supports: every pledge is a single for in pass with an accumulator.

  • sum(xs) and product(xs) return Result<Int, CommonError>; product refuses to wrap by checking each multiply against the Int bounds before it runs and answers Err(Invalid) on overflow.
  • min_of(xs) and max_of(xs) return Option<Int> and answer None on the empty list, since a reduction that needs a first element to seed has no answer over nothing.
  • contains(xs, want) and all_eq(xs, want) return Result<Bool, CommonError>.
  • count_of(xs, want) returns Result<Int, CommonError>.
  • index_of(xs, want) returns Option<Int>, counting slots as it walks and answering None on a miss.

A generic ListOps, and any operation that builds a new list, waits on the surface growing generics and a push.

The shared provisional clauses. A provisional clause is a clause template, so each one here is a set of signatures a contract incorporates and must implement exactly; nothing in this file has a body.

provisional clause Loggable {
clause log_line(msg: String) -> String
}
provisional clause Comparable {
clause compare(a: Int, b: Int) -> Int
}
provisional clause Serializable {
clause serialize_tag() -> String
}

Loggable turns a message into the line the implementor would log, a pure decoration since a clause has no IO to reach. Comparable is the ordering hook, negative, zero, or positive the way every comparator has ever spoken. Serializable names the tag a value would serialize under.

These stay internal code sharing for now, since a provisional clause cannot yet require pledges. Until that lands, incorporating one shares clause signatures inside a contract but does not create a public interface a host can call.

The store surface a store backed contract reads and writes through. Store is not a contract and not a value; it is a namespace the compiler knows, so its operations name a schema declared in the same contract and lower onto the runtime primitives that carry a row across the boundary. A row is the schema’s record, its fields the columns in declaration order, owned by the instance and dead at its break. The first column of a schema is its primary key.

The operations and their shapes:

Store.find(S, key) -> Result<Option<Row>, StoreError>
Store.query(S, column, value) -> Result<List<Row>, StoreError>
Store.query(S, predicate) -> Result<List<Row>, StoreError>
Store.query(S, predicate, asc(column)) -> Result<List<Row>, StoreError>
Store.query(S, predicate, desc(column)) -> Result<List<Row>, StoreError>
Store.query(S, predicate, desc(column), limit(n)) -> Result<List<Row>, StoreError>
Store.count(S, predicate) -> Result<Int, StoreError>
Store.sum(S, column, predicate) -> Result<Int|Float, StoreError>
Store.min(S, column, predicate) -> Result<Option<Col>, StoreError>
Store.max(S, column, predicate) -> Result<Option<Col>, StoreError>
Store.avg(S, column, predicate) -> Result<Option<Float>, StoreError>
Store.insert(S, row) -> Result<Unit, StoreError>
Store.update(S, key, row) -> Result<Unit, StoreError>
Store.delete(S, key) -> Result<Unit, StoreError>

A predicate is a boolean tree of comparisons over schema columns, joined with && and ||, negated with !, which the compiler normalizes before it reaches the store. Every value binds positionally, never by concatenation, so a value carrying SQL is a value and nothing else. count, sum, min, max, and avg aggregate behind the boundary; the rows are never materialized. The aggregates that have no zero answer an Option: an empty set has no smallest member, no largest, and no mean, so min, max, and avg answer None over an empty match while sum keeps its zero.

The error sum names the two ways the surface itself can refuse a value before the backend is even asked:

StoreError is either StoreDecode or StoreShape

A backend failure the runtime cannot complete, a lost connection, a full disk, a refused constraint, is the GEAS_ERR_STORE status delivered through the fulfillment, never folded into a contract’s own error type.