Type system
Geas is statically typed with fixed representations that are identical on every target. There is no implicit conversion anywhere in the language, and every value carries value semantics with exactly one named exception, a signed contract instance.
Primitive types
Section titled “Primitive types”| type | representation |
|---|---|
Unit | the empty value |
Int | 64 bit signed |
UInt | 64 bit unsigned |
Float | IEEE 754 double |
Bool | one byte, true or false |
Byte | one byte |
Char | a 32 bit Unicode scalar value |
String | UTF-8 bytes, a pointer and a byte length, no terminator |
Numeric widths are fixed and never convert implicitly. A widening or narrowing is a type error unless written explicitly, and there is no implicit conversion anywhere in the language, widths included. An Int never becomes a Float on its own, and a Byte never widens to an Int quietly.
String + String concatenates into a new String. That is the one operator with an arm that is not numeric; a String never meets a numeric operand.
Composite types
Section titled “Composite types”List<T>is a homogeneous, ordered sequence, written[a, b, c].- Tuples are a fixed heterogeneous group whose elements need not agree in type.
Map<K, V>is an ordered map, constructedMap<K, V>(), with a keyable scalar key. An index read answersOption<V>,Noneon a miss, and an index assignment inserts or updates. Entries keep insertion order, and that order is what serialization sees and what equality compares.
There is no map literal. A map starts empty from its constructor form and grows through index assignment:
let mut ages = Map<String, Int>()ages["ada"] = 36let hit = ages["ada"] // Option<Int>, Some(36)let miss = ages["bob"] // NoneType arguments belong to the builtin composites only. A user declaration does not take type parameters in the current version.
Records
Section titled “Records”A record is a product type, a fixed set of named fields:
record Card { number: String amount: Float}A record is built with a brace literal on the type’s name, its fields read by name, and it carries value semantics like every composite. A record is plain data with no lifecycle, copyable across the ABI.
Sum types
Section titled “Sum types”A sum is a choice between variants, each of which may carry a payload, written is either:
DemoError is either BadMood(reason: String) or EmptyA variant is built by naming it, BadMood("asked to fail") or Empty, and taken apart with match. A sum’s representation carries the variant’s declaration index as its tag. A sum declaration is one statement, so its or chain stays on one line.
LogLevel is either Debug or Info or ErrorMathError is either DivByZero or Overflow(detail: String)Error types are ordinary sums used in the E slot of Result. There is no special error machinery; a failure is a value of a sum type.
Option and Result
Section titled “Option and Result”Option<T> is Some(T) or None. Result<T, E> is Ok(T) or Err(E). They are how nullability and failure are expressed, and the checker holds a match on either to both arms:
match r { Ok(v) -> { return Ok(v * 2) } Err(_) -> { return Err(-1) }}Every fallible pledge returns a Result, an Option, or Unit, never a bare fallible value. ? propagates: f(x)? runs a fallible call, returns its Err from the enclosing pledge immediately, and unwraps its Ok otherwise.
Bindings: let and mut
Section titled “Bindings: let and mut”let binds an immutable name, let mut a reassignable one:
let n = 0let mut xs = [a, b, c]Assignment targets a let mut binding, a field of one, or an element of one. Vows are never assignable; mut(expr) is the builtin that produces a mutable copy of a vow value, and the original never changes.
Value semantics
Section titled “Value semantics”Composites carry value semantics at every boundary. A let of a composite, an assignment, and a composite read all deep copy, so mutation through one binding is never visible through another:
let b = aon a record or list givesban independent copy.- Passing a composite as an argument copies it.
- Returning a composite copies it home.
The one place a write lands in place is an assignment target, xs[0] = y or record.field = y or map[k] = y, which walks to the named slot and writes there while the right side still copies. An out of range list index or a missing map key mid chain is a clean type error, never a fault, and the fault checks run before the right side evaluates.
The one exception: a signed instance
Section titled “The one exception: a signed instance”A signed contract instance is the one value in the language with reference semantics. A copy of the handle aliases the same instance, equality on instances is identity, and there is no way to copy a contract:
let ops = MathOps.sign(epsilon: 0.5)let same = ops // aliases the same instanceA signed instance is also the one value that can never cross the C boundary; only its results can. A contract type is forbidden at every ABI position: a pledge parameter or return, a vow, a record or sum field, and a provisional clause signature, at any nesting depth. Clause parameters, clause returns, and local bindings may carry contract types freely, since they never leave the module.
A pledge called on another contract inside a body runs a real fulfillment on that instance, and its result is copied home before it is used, so the value survives the callee breaking. See Contracts and the lifecycle.
Related pages
Section titled “Related pages”- Grammar has the full type grammar and literal rules.
- Requirements and state covers how pledge outcomes drive contract state.
- Interop covers the fixed boundary representation of each type.