#Host
Status: standard library specification draft.
This chapter defines std.host, which lib/std writes in ordinary hd
over the language tier:
- the host capability traits
Args,Env, andSys; - the helpers
args,env,env_as, andenv_decode, and the sharedHostError; ScriptedArgs,ScriptedEnv, andScriptedSys, their deterministic providers;- what every host operation does while it waits, when it is cancelled, and with the data it holds.
Which provider a command binds for each trait is CLI tier (Host Capabilities).
#Program Arguments
Args reads the arguments the program was started with:
pub trait Args:
fn program(self) -> string
fn list(self) -> List[string]- std-host.args.decl
std.hostdeclares the host capability traitArgswith the methods above. Code imports it, as inuse std.host.Args. - std-host.args.program
programreturns the name the program was started with. - std-host.args.list
listreturns the program's arguments, in order, without the program name. - std-host.args.helper
std.hostdeclarespub fn args() -> List[string] $ Args, which returnslist()of theArgsprovider that covers the call.
#Environment
Env reads environment variables:
pub trait Env:
fn get(self, name: string) -> string?
fn names(self) -> List[string]- std-host.env.decl
std.hostdeclares the host capability traitEnvwith the methods above. Code imports it, as inuse std.host.Env. - std-host.env.get
get(name)returns the value of the variablename, or.Nonewhen it is not set. - std-host.env.names
namesreturns the name of every set variable, each once. - std-host.env.helper
std.hostdeclarespub fn env(name: string) -> string? $ Env, which returnsget(name)of theEnvprovider that covers the call.
Note. Under an
Envgrant, the default profile'sgetreturns.Nonefor a variable outside the grant, andnamesleaves it out (Environment Grant).
#Typed Variables
env_as reads a variable as any type that implements
Parse:
use std.host.{Env, HostError, env_as}
fn workers() -> Result[usize, HostError] $ Env:
.Ok(env_as::[usize]("WORKERS")?.unwrap_or(4))pub enum HostError:
NotGranted(subject: string)
Unsupported(name: string)
Other(message: string)
InvalidValue(name: string, reason: string)
Missing(name: string)- std-host.env-as.shared-error-signature
std.hostdeclarespub fn env_as[T < Parse[Error = E], E < Display](name: string) -> Result[T?, HostError] $ Env. The call names the type, as inenv_as::[usize]("WORKERS"). - std-host.env-as.unsetWhen
env(name)is.None,env_asreturns.Ok(.None). - std-host.env-as.presentWhen
env(name)is.Some(text)andT::parse(text)returns.Ok(value),env_asreturns.Ok(.Some(value)). - std-host.env-as.invalid-host-errorWhen
T::parse(text)returns.Err(error),env_asreturns.Err(HostError.InvalidValue(name, reason)).reasonis theDisplaytext oferror. - std-host.error.typed-env-decl
std.hostdeclaresHostErrorwith the five variants above. - std-host.env-as.plain
env_asis a plain call, likeenv. - std-host.error.traits
HostErrorimplementsEq,Debug,Display, andError. - std-host.error.invalid-value-display
InvalidValue(name, reason)displaysinvalid value for NAME: REASON. - std-host.error.not-granted-display
NotGranted(subject)displayssys access to SUBJECT is not granted. - std-host.error.unsupported-display
Unsupported(name)displayssys access to NAME is not supported. - std-host.error.other-display
Other(message)displaysmessageunchanged.
Why. An unset variable is normal, so it is
.Ok(.None), as Go'sos.LookupEnvreports it apart from an empty value. A set variable that does not parse is a mistake the program should show, so it is an error that names the variable, asstrconverrors name the text.
#Typed Records
env_decode reads a record through the existing
Deserialize protocol.
use std.host.{Env, HostError, env_decode}
use std.serde.Deserialize
@derive(Deserialize)
struct Config:
listen_port: u16 = 8080
optional_label: string?
fn config() -> Result[Config, HostError] $ Env:
env_decode::[Config]()- std-host.env-decode.decl
std.hostdeclarespub fn env_decode[T < Deserialize]() -> Result[T, HostError] $ Env. - std-host.env-decode.record-rootThe root value must be a struct with named fields.
- std-host.env-decode.root-errorA non-record root returns
HostError.Other("env_decode needs a struct with named fields"). - std-host.env-decode.field-nameEach field's environment name defaults to its source name converted to uppercase.
- std-host.env-decode.name-fact
std.hostdeclares the non-generic field factEnvNamewith the public fieldname: string. - std-host.env-decode.name-constructor
std.hostdeclarespub fn env_name(name: string) -> EnvName, which returnsEnvName { name: name }. - std-host.env-decode.override-nameA field's
EnvName.name, written as@env_name("REGIONS"), replaces its default environment name. - std-host.env-decode.names-independentEnvironment names are independent of serde's document-name facts.
- std-host.env-decode.empty-nameAn empty
EnvName.namereturnsHostError.InvalidValue(field, "empty environment name"), wherefieldis the source field name. - std-host.env-decode.empty-name-no-readAn empty
EnvName.namemust be rejected before reading that environment variable. - std-host.env-decode.declared-readsDecoding reads only the fields' selected names through
Env.get, without callingEnv.names. - std-host.env-decode.unset-nullAn unset variable is an absent, null value to the deserializer.
- std-host.env-decode.optionalWithout a declared default, an unset variable for an optional field becomes
.None. - std-host.env-decode.defaultA field whose variable is unset uses its declared default when one is present.
- std-host.env-decode.missingAn absent required field without a declared default returns
HostError.Missing(name), including when the field is a list. - std-host.env-decode.scalarScalar text is read through the deserializer's
bool,int,uint,float, andtextmethods. - std-host.env-decode.list-separatorA list field splits its variable's text on commas.
- std-host.env-decode.list-no-trimSplitting a list does not trim its items.
- std-host.env-decode.list-emptyAn explicitly empty variable for a list decodes as
[]. - std-host.env-decode.invalid-valueA refused variable value returns
HostError.InvalidValue(name, reason), naming the variable and the refusal reason. - std-host.env-decode.custom-deserializeA field's custom
Deserializeimplementation reads through the same environment deserializer. - std-host.env-decode.plain
env_decodeis a plain call. - std-host.error.missing-display
Missing(name)displaysmissing environment variable NAME.
Why. Environment and document formats share typed deserialization, while separate field facts keep environment variable names independent.
#Plain Reads
- std-host.plain-readsEvery method of
ArgsandEnv, and both helpers, are plain calls, not bang calls.
use std.host.{Args, Env, args, env}
fn greeting() -> string $ Args + Env:
let names = args()
match env("GREETING"):
.Some(word) => "${word}, ${names.len()}"
.None => "hello"Why. An argument or environment read returns a value the host already holds, so it needs no driver. Replay records it at the boundary either way.
#Map Providers
ScriptedArgs and ScriptedEnv are the deterministic providers of Args and
Env. A test supplies them with $.with:
use std.host.{Args, Env, ScriptedArgs, ScriptedEnv, args, env}
fn greeting() -> string $ Args + Env:
match env("GREETING"):
.Some(word) => "${word}, ${args().len()}"
.None => "hello"
tests:
use std.testing.assert_equal
it("greets from fixed input"):
given := ScriptedArgs::new("tool", ["a", "b"])
vars := ScriptedEnv::new({"GREETING": "hi", "HOME": "/home/me"})
$.with(Args=given, Env=vars):
assert_equal(greeting(), "hi, 2", reason="one variable, two arguments")
assert_equal(vars.names().sorted(), ["GREETING", "HOME"], reason="both keys")- std-host.map-args.decl
std.hostdeclaresScriptedArgs, which implementsArgs, with private fields. Code imports it, as inuse std.host.ScriptedArgs. - std-host.map-args.new
ScriptedArgs::new(program: string, values: List[string]) -> ScriptedArgsreturns a provider whoseprogramreturnsprogramand whoselistreturnsvalues. - std-host.map-env.decl
std.hostdeclaresScriptedEnv, which implementsEnv, with private fields. Code imports it, as inuse std.host.ScriptedEnv. - std-host.map-env.new
ScriptedEnv::new(values: Map[string, string]) -> ScriptedEnvreturns a provider whoseget(name)returns the value of the keynameinvalues, or.Nonewhen there is no such key. - std-host.map-env.names.order
nameson aScriptedEnvreturns the keys ofvaluesin the map's iteration order. - std-host.map.no-hostNeither provider reads the host's arguments or environment.
Why. A test states every argument and variable that the code under test reads, so nothing leaks in from the machine that runs it. Effect's
ConfigProvider.fromMapserves tests the same way.
#System Reads
Sys reads four facts about the host, as Go's runtime.GOOS,
runtime.GOARCH, runtime.NumCPU, and os.Hostname do:
pub trait Sys:
fn os(self) -> Result[string, HostError]
fn arch(self) -> Result[string, HostError]
fn hostname(self) -> Result[string, HostError]
fn cpu_count(self) -> Result[u32, HostError]- std-host.sys.decl
std.hostdeclares the host capability traitSysas above. Code imports it, as inuse std.host.Sys. - std-host.sys.os
osreturns the operating system's name in lowercase, such aslinux,macos, orwindows. - std-host.sys.arch
archreturns the host machine's architecture, such asx86_64oraarch64. - std-host.sys.hostname
hostnamereturns the host's name. - std-host.sys.cpu-count
cpu_countreturns the number of logical processors that the host offers the program. - std-host.sys.plainEvery method of
Sysis a plain call, not a bang call, as the reads ofEnvare bystd-host.plain-reads. - std-host.sys.not-grantedA method returns
.Err(HostError.NotGranted(subject))when the program's capability grant does not cover it, with the method's name forsubject. - std-host.sys.unsupportedA method returns
.Err(HostError.Unsupported(name))when the host can't answer it, as a browser can't forhostname.
Why. The grant covers each read by its method's name, as Deno gates each
syscall, so each method returns aResult.
#Map Sys
ScriptedSys is the deterministic Sys provider. A test supplies it with
$.with:
use std.host.{ScriptedSys, Sys, HostError}
fn platform() -> string $ Sys:
match $.use(Sys).os():
.Ok(name) => name
.Err(_) => "unknown"
tests:
use std.testing.assert_equal
it("reads the mapped system"):
$.with(Sys=ScriptedSys::new({"os": "linux", "cpu_count": "8"})):
assert_equal(platform(), "linux", reason="the mapped os")
assert_equal($.use(Sys).cpu_count(), .Ok(8), reason="parsed as a count")
assert_equal($.use(Sys).hostname(), .Err(HostError.Unsupported("hostname")), reason="not in the map")- std-host.sys.map.decl
std.hostdeclaresScriptedSys, which implementsSys, with private fields. Code imports it, as inuse std.host.ScriptedSys. - std-host.sys.map.new
ScriptedSys::new(values: Map[string, string]) -> ScriptedSysreturns a provider whose methods each return the value of the key that is the method's name. - std-host.sys.map.cpu-count
cpu_countreads its value as a decimalu32. - std-host.sys.map.missingA method whose key is missing, or whose value
cpu_countcan't read, returns.Err(HostError.Unsupported(name)). - std-host.sys.map.no-hostA
ScriptedSysnever reads the host system.
#Host Operations
A host operation is a bang method call that the host completes, such as
send! on the host's Http provider. While one waits, the instance runs
its other tasks:
use std.http.{Http, HttpError, Response, get}
use std.task.all
fn both!(left: string, right: string) -> (Result[Response, HttpError], Result[Response, HttpError]) $ Http:
all!(get(left), get(right)) # both requests wait at the same time- std-host.op.waitA bang host method that waits lets the instance run its other tasks until it completes.
- std-host.op.no-orderIndependent host operations that complete together have no defined order, so the instance may resume their tasks in any order.
- std-host.op.cancelCancelling the suspension of a pending host operation ends that operation. Each trait states what the operation leaves behind, as the table below lists.
- std-host.op.cancel.writeA cancelled host operation that writes may have happened, in whole or in part.
| Trait | A cancelled operation leaves |
|---|---|
Process |
no child: it and its process group are killed, by std-process.cancel |
Http |
no connection: it is closed, by std-http.send.cancel.close |
Net |
the bytes not yet returned, for the next read, by std-net.cancel.read |
FsWrite |
possibly a partial file, by std-fs.cancel.write |
Why. A
race!timeout around a host call is portable only when the call lets the timer run. Completions that arrive together may reach the instance in one batch, so no program may rely on their order.
See also: Program Arguments, Entry Arguments.