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

std::env

What a program was told: its arguments and its environment.

The smallest thing that separates a program from a demo. Until this existed, nothing a Khora program did could depend on anything outside its own source — every path, every port, every setting was compiled in.

The Clock capability is in std::clock, not here. This file is native-only for getenv and argv; a clock has no such constraint, and grouping the two would have imposed one for a reason that has nothing to do with clocks.

The C side is almost all ISO C: getenv, strlen, memcpy. The one exception is the argument vector, which no C function returns — it arrives through main and is gone, so the generated main hands it to the runtime and khora_arg gives it back.

Types: EnvError

Effects: Env

Methods: Env

Functions: variable_or

derive(Show, Eq)
pub type EnvError = | Denied(name: String);

Why reading the environment did not work.

One case, because there is one way for it to fail that is not “no such variable”: the manifest did not grant it. Not being set is None, which is an answer rather than a failure.

pub effect Env {
variable: (String) -> Option<String> raises EnvError,
arguments: () -> List<String>,
}

What the process was started with.

A capability rather than plain functions, for the reason every other one is: a function that reads DATABASE_URL says so in its signature, and a test can hand it something else. Reading the environment is exactly the kind of hidden input that makes a program hard to test, and this is the seam.

variable: (String) -> Option raises EnvError

The value of a variable, or None when it is not set.

raises because a denial is not None. A variable the manifest does not grant is a different thing from one nobody set, and the fix is a line in khora.toml rather than a line in a deployment script. Answering None for both would send a reader to look at their environment for a decision their own manifest made – the same reasoning IoError::Denied records.

arguments: () -> List

Everything the process was invoked with, the program’s own name first — the same convention C, Rust and Go all follow.

impl Env
pub fn real() -> Env

The real environment.

pub fn variable_or(name: String, fallback: String) -> String with { env: Env } raises EnvError

A variable, or a default when it is not set.

The shape almost every caller wants, written once here rather than as a match at each of them.

The fallback is for “not set”, not for “not allowed”. A denial passes through rather than quietly becoming the default: a program told it may not read DATABASE_URL and silently given localhost is a program that starts and does the wrong thing, which is worse than one that stops and says why.

import std::env::{Env, variable_or};
fn port() -> Int with { env: Env } raises EnvError {
match Int::of_string(variable_or("PORT", "8080")!) {
Option::Some(n) => n,
Option::None => 8080,
}
}

The ! is there because a denial is a failure rather than a missing value, which is the distinction the paragraph above is about.

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