C hosts
A C host is a foreign program. It knows nothing about the geas compiler; it links libgeasrt, includes geas/geas.h, and speaks the ABI. The repository’s skeleton/host.c is the reference walk, and every snippet on this page comes from it. The shape of a host is always the same five moves described in the interop overview.
Initialize and load
Section titled “Initialize and load”GeasRuntime* rt = NULL;geas_runtime_init(NULL, &rt);geas_module_load(rt, "target/geas-out/libhello.geas.so");A NULL config selects the defaults, including a pool of four workers. geas_module_load dlopens the module, resolves its registrar, and registers every contract descriptor the module carries.
Bind an abstract pledge
Section titled “Bind an abstract pledge”Greeter.shout is declared with no body. A contract with any unbound abstract pledge refuses to sign with GEAS_ERR_UNBOUND, so the host supplies the implementation first, written in the same uniform thunk frame every compiled pledge uses:
static GeasStatus host_shout(void* ctx, const GeasValue* args, size_t nargs, GeasValue* out) { GeasContract* c = (GeasContract*)ctx; // uppercase args[0], write the result into *out as Ok(...) return GEAS_OK;}
geas_pledge_bind(rt, "Greeter.shout", host_shout);ctx is the signed instance the fulfillment runs against, and everything the body builds through the allocation helpers, geas_box, geas_string_copy, and friends, is owned by that instance and dies with it. Binding an unknown pledge is GEAS_ERR_NAME, and signing before the bind is GEAS_ERR_UNBOUND, both checked in host.c:
GeasContract* c0 = NULL;if (geas_contract_sign(rt, "Greeter", NULL, 0, 0, &c0) != GEAS_ERR_UNBOUND) { geas_runtime_shutdown(rt); return fail("sign before bind did not report GEAS_ERR_UNBOUND");}Discover through the iname table
Section titled “Discover through the iname table”The iname table turns a mangled name into the owning contract and the shape hash to sign under, so the first sign is a checked handshake instead of a bare string:
GeasInameEntry ent;if (geas_iname_lookup(rt, GREET_MANGLED, &ent) != GEAS_OK) { geas_runtime_shutdown(rt); return fail("iname lookup of the greet mangled name");}The entry carries the kind, the owning contract name, the pledge name, the argument count, the version, and the shape hash. Lookup is exact: the whole mangled name or GEAS_ERR_NAME, so a stale name misses cleanly instead of resolving to the wrong shape.
Sign under the discovered contract name and hash. An expected_hash of 0 skips the check; a nonzero hash that disagrees with the loaded module is GEAS_ERR_VERSION before any instance exists:
GeasContract* c = NULL;if (geas_contract_sign(rt, ent.contract, NULL, 0, ent.shape_hash, &c) != GEAS_OK) { geas_runtime_shutdown(rt); return fail("sign under the discovered contract and hash");}To override a vow at sign, pass a GeasVowBinding. The runtime copies the value onto the instance and keeps nothing of the host’s:
GeasVowBinding prefix;prefix.name = "prefix";prefix.value = str_arg("hey, ");GeasContract* c3 = NULL;if (geas_contract_sign(rt, "Greeter", &prefix, 1, 0, &c3) != GEAS_OK) { geas_runtime_shutdown(rt); return fail("sign with vow override");}An override naming no vow is GEAS_ERR_NAME, an override of the wrong type is GEAS_ERR_TYPE, and no failure leaves an instance behind. The host reads a vow back the same way a thunk does, through geas_vow_ref, which returns a pointer the instance owns.
Fulfill synchronously and read the result
Section titled “Fulfill synchronously and read the result”The argument is host owned; the runtime deep copies it at the call and never keeps it:
GeasValue name = str_arg("world");
GeasValue out;if (geas_pledge_fulfill_sync(c, "greet", &name, 1, NULL, 0, &out) != GEAS_OK) { geas_runtime_shutdown(rt); return fail("fulfill greet");}The result is a Result value: out.ty is GEAS_TY_RESULT, out.tag is 0 for Ok and 1 for Err, and the payload rides behind out.as.box as a single GeasValue the instance owns. host.c checks it like this:
static int check_ok_string(const GeasValue* out, const char* want) { if (out->ty != GEAS_TY_RESULT || out->tag != 0) return 0; const GeasValue* inner = (const GeasValue*)out->as.box; if (!inner || inner->ty != GEAS_TY_STRING) return 0; if (inner->as.s.len != strlen(want)) return 0; return memcmp(inner->as.s.ptr, want, inner->as.s.len) == 0;}A pledge that answers Err still returns GEAS_OK from the call: the status reports that the fulfillment ran, the value reports what the pledge decided.
By reference and write back
Section titled “By reference and write back”A pledge can take a trailing argument by reference and hand data back through the host’s own pointer. The host owns the storage, the runtime copies the value in at fulfill, the body mutates its instance owned slot, and the write back lands in host memory at delivery, while the host is still blocked in the call:
GeasString by_ref;by_ref.ptr = (uint8_t*)"whisper";by_ref.len = 7;
GeasRef ref;memset(&ref, 0, sizeof(ref));ref.host_ptr = &by_ref;ref.ty = GEAS_TY_STRING;
if (geas_pledge_fulfill_sync(c, "shout", NULL, 0, &ref, 1, &out) != GEAS_OK) { geas_runtime_shutdown(rt); return fail("fulfill shout through a ref");}After the call, by_ref points at the shouted bytes. The default write back points the host’s GeasString at instance owned bytes; a host that wants the bytes to outlive the instance supplies a write_back callback that copies them into storage it owns.
Futures
Section titled “Futures”geas_pledge_fulfill returns a handle and geas_future_wait collects the outcome exactly once. A second wait is a clean state error:
GeasFuture* f = geas_pledge_fulfill(c3, "greet", &name, 1, NULL, 0);if (!f) { geas_runtime_shutdown(rt); return fail("fulfill returned no future");}GeasValue fout;if (geas_future_wait(f, &fout) != GEAS_OK) { geas_runtime_shutdown(rt); return fail("future wait");}if (geas_future_wait(f, &fout) != GEAS_ERR_STATE) { geas_runtime_shutdown(rt); return fail("double wait did not report GEAS_ERR_STATE");}Every fulfillment error is delivered by the wait, not the fulfill, and the ref write back happens inside the delivering wait on the waiting thread. The value’s contents are owned by the instance that produced it, so wait before you break.
Tearing the contract down reclaims everything the instance allocated, and every later fulfillment on it is a clean state error:
geas_contract_break(c);if (geas_pledge_fulfill_sync(c, "greet", &name, 1, NULL, 0, &scratch) != GEAS_ERR_STATE) { geas_runtime_shutdown(rt); return fail("fulfill after break did not report GEAS_ERR_STATE");}A break also forfeits every future not yet waited to GEAS_ERR_STATE, so a fulfillment racing a break lands on exactly one of two outcomes and never touches freed memory.
Never hardcode a string
Section titled “Never hardcode a string”A host can spell mangled names by hand, but the compiler will generate the header:
geas emit-header skeleton/hello.geasThat writes target/geas-out/hello.geas.h, which spells the shape hash and every mangled pledge name as defines, read from the same tables the module emitter hashes, so a header and its module cannot disagree:
/* contract Greeter, version 1 * vow prefix: String (default) */#define GEAS_HASH_Greeter 0x80464bf23398ab38ULL
/* pledge greet(name: String) -> Result<String, GreetError> */#define GEAS_MANGLED_Greeter_greet "__geas_ash_Greeter_greet_17cef80f14421b9b_v1"The host resolves GEAS_MANGLED_Greeter_greet through the iname table and signs under GEAS_HASH_Greeter, and no string literal in the host can drift from the module.
Environment
Section titled “Environment”Two environment variables let you run geas from anywhere instead of the repository root:
GEAS_ROOTpoints cc at the runtime headers andlibgeasrtwhen building modules and hosts.GEAS_HOMEpoints the compiler at the standard library for imports.
Run the whole walk
Section titled “Run the whole walk”The repository gates the entire C surface, including the sanitized run:
make smokemake smoke-asanmake smoke builds the runtime, the compiler, the module, and the host, then runs the host end to end: bind, discovery, sign, vow override, fulfill, by reference write back, futures, break, freeze, and the iname dump. Continue with Python hosts to see the same contract driven with no C written at all.