
If the code will be machine-generated, the language should make entire categories of failure structurally impossible. No null. No exceptions. No side effects unless the host allows them.
Defensive code you write because the language allows it
async def classify_and_route(task: str) -> dict:
# Call LLM — might throw, might return None
try:
response = await llm.classify(task)
except Exception as e:
return {"error": f"Classification failed: {e}"}
# Response might be None, might be wrong type
if response is None:
return {"error": "No response from classifier"}
if not isinstance(response.category, str):
return {"error": f"Expected string, got {type(response.category)}"}
# Route based on category
handlers = {
"billing": handle_billing,
"technical": handle_technical,
"general": handle_general,
}
handler = handlers.get(response.category, handle_general)
try:
result = await handler(task)
except Exception as e:
return {"error": f"Handler failed: {e}"}
if result is None:
return {"error": "Handler returned None"}
return resultWhat's left when the failure modes are structurally impossible
# No null. No exceptions. No wrong types.
# If it parses, it's safe to run.
$task
-> host::classify
-> .category:string
-> [
billing: host::handle_billing,
technical: host::handle_technical,
general: host::handle_general
] ?? host::handle_general
-> $($task)try/except
→
no exceptionsis None
→
no nullisinstance()
→
types locked at assignmentGeneral-purpose languages leave guarantees on the table. rill was designed as a target for LLM code generation.
No filesystem, no network, no side effects unless the host allows them.
No null, no exceptions, no implicit coercion. Entire categories of failure don't exist here.
Zero dependencies. Runs in Node, Bun, Deno, or the browser.
Swap providers by changing one line. Scripts stay identical.