Stdlib typed errors

Every fallible stdlib module declares its own typed error enum. Silt’s Result(T, ModuleError) return shape lets user code pattern-match the failure modes directly instead of substring-matching on a String payload. Each enum implements the built-in Error trait, which supertypes Display and provides a message() method so code that just wants a rendered error can fall back to "{e.message()}".

Variant naming

Every error enum is named after its module (io.IoError, tcp.TcpError), its variants carry no prefix (io.NotFound, tcp.Timeout), and its last variant is Other(String) for a failure that fits no other. An error enum and its variants are members of their module, reached through it, with or without the enum as a second qualifier:

import io

let a = io.NotFound("config.toml")

let b = io.IoError.NotFound("config.toml") -- same value

A selective import binds a variant by its bare name: import io.{ NotFound }. Two modules may have a variant of one name (tcp.Timeout and http.Timeout, Other in every module): a file imports at most one of them bare, and writes the others through their modules.

Enums

IoError (requires import io)

VariantFieldsMeaning
NotFound(String)pathfile or directory missing
PermissionDenied(String)pathpermissions check failed
AlreadyExists(String)pathtarget already exists
InvalidInput(String)descriptionmalformed argument
Interrupted—syscall interrupted
UnexpectedEof—reader hit EOF mid-record
WriteZero—writer returned zero bytes
Other(String)platform messageunclassified platform error

JsonError (requires import json)

VariantFieldsMeaning
Syntax(String, Int)message, byte offsetsyntactically invalid JSON
TypeMismatch(String, String)expected, actualwrong JSON type for target
MissingField(String)field namerequired field absent
Other(String)messageunclassified parse failure

TomlError (requires import toml)

VariantFieldsMeaning
Syntax(String, Int)message, byte offsetsyntactically invalid TOML
TypeMismatch(String, String)expected, actualwrong TOML type for target
MissingField(String)field namerequired field absent
Other(String)messageunclassified parse failure

IntError (requires import int)

Returned by int.parse.

VariantFieldsMeaning
Empty—input was empty
InvalidDigit(Int)byte offsetnon-digit character at offset
Overflow—value exceeds type max
Underflow—value below type min
Other(String)messageunclassified parse failure

FloatError (requires import float)

Returned by float.parse: the same variants as IntError, in a type of its own (float.Empty is not int.Empty).

VariantFieldsMeaning
Empty—input was empty
InvalidDigit(Int)byte offsetcharacter that is no part of a number at offset; "NaN" and "inf" at 0
Overflow—literal above the Float range
Underflow—literal below the Float range
Other(String)messageunclassified parse failure

HttpError (requires import http)

VariantFieldsMeaning
Connect(String)messageno connection was made: refused, no route, no such host
Tls(String)messageTLS handshake / cert failure
Timeout—one of the request’s times passed
InvalidUrl(String)urlthe URL cannot be read as one, has no host, has a scheme other than http and https, or names a port that is none
InvalidResponse(String)messagewhat the server sent is no HTTP, or its body is larger than 10 MiB
ClosedEarly—the server went away before the response was complete
Other(String)messageunclassified transport error

TcpError (requires import tcp)

VariantFieldsMeaning
Connect(String)messagetcp.connect made no connection (refused, unreachable, no such host)
Bind(String)messagetcp.listen bound no listener (address in use, not this machine’s, no address)
Tls(String)messageTLS handshake failure
Closed—connection closed, by this end or the other (also a reset, an abort, a broken pipe)
Timeout—op exceeded its deadline
Other(String)messageunclassified socket failure

PostgresError (requires import postgres)

VariantFieldsMeaning
Connect(String)messagetcp / DNS / pool checkout failure
Tls(String)messageTLS setup / handshake
AuthFailed(String)messageSQLSTATE class 28
Query(String, String)message, SQLSTATEserver-reported error
TypeMismatch(String, String, String)column, expected, actualrow decode
NoSuchColumn(String)columnrow.get on missing column
Closed—connection dropped mid-query
Timeout—statement / pool timeout
TxnAborted—SQLSTATE 25P02 — rollback required
Other(String)messageunclassified pg error

TimeError (requires import time)

VariantFieldsMeaning
ParseFormat(String)messagethe text did not match a pattern that could have given the value
OutOfRange(String)messagefield out of valid range (e.g. month=13)
InvalidFormat(String)what is wrongthe pattern of time.format, time.format_date, time.parse or time.parse_date is no format, or one from which no text could give the value (no time of day for a DateTime, no date, empty)
Other(String)messageunclassified failure

BytesError (requires import bytes)

VariantFieldsMeaning
InvalidUtf8(Int)byte offsetdecode failed at offset
InvalidHex(String)messagebad hex string
InvalidBase64(String)messagebad base64 string
ByteOutOfRange(Int)valuelist element outside 0..=255
Other(String)messageunclassified failure

TaskError (requires import task)

Returned by task.deadline.

VariantFieldsMeaning
Timeout—the function was ended because its time was over
Other(String)messageunclassified failure

EncodingError (requires import encoding)

Returned by encoding.url_decode and encoding.form_decode.

VariantFieldsMeaning
InvalidPercent(String)what and wherea % that two hex digits do not follow
InvalidUtf8(String)wheredecoded bytes are not valid UTF-8
Other(String)messageunclassified failure

CryptoError (requires import crypto)

Returned by crypto.random_bytes.

VariantFieldsMeaning
InvalidLength(Int)the length asked fornegative, or above the 1 MiB cap
Other(String)messagethe OS CSPRNG failed

UuidError (requires import uuid)

Returned by uuid.parse.

VariantFieldsMeaning
Invalid(String)whynot a syntactically valid UUID
Other(String)messageunclassified failure

RegexError (requires import regex)

Returned by every regex.* function for a pattern that does not compile.

VariantFieldsMeaning
InvalidPattern(String, Int)what is wrong, and the character of the pattern where: counted from 0, as string.slice countspattern did not parse
TooBig—compiled pattern exceeded size budget
Other(String)messageunclassified failure

Example: user-side pattern matching

import io

fn handle(e: io.IoError) -> String {
  match e {
    io.NotFound(path) -> "missing: {path}"
    io.PermissionDenied(path) -> "denied: {path}"
    io.AlreadyExists(_) | io.InvalidInput(_) -> "recoverable"
    io.Interrupted | io.UnexpectedEof | io.WriteZero -> "transient"
    io.Other(msg) -> "unknown: {msg}"
  }
}

fn main() {
  println(handle(io.NotFound("config.toml")))
}
open in playground

Cross-module composition

Silt does not auto-convert between error types. A function that spans several stdlib modules wraps each module’s error in a local AppError enum and lifts each call’s Err into it with result.map_err. Variant constructors are first-class Fn(e) -> f values, so the second argument is just the constructor name — no closure wrapper needed:

import io
import json
import result

type Config {
  host: String,
  port: Int,
}

type AppError {
  IoProblem(io.IoError),
  JsonProblem(json.JsonError),
}

fn load_config(path: String) -> Result(Config, AppError) {
  let raw = io.read_file(path) |> result.map_err(IoProblem)?
  let cfg = json.parse(raw, Config) |> result.map_err(JsonProblem)?
  Ok(cfg)
}

A ? that ends a pipeline applies to the whole pipeline, so the pipeline is a single expression terminated by ?. See examples/cross_module_errors.silt for a longer walkthrough. A separate proposal (error-from-trait.md) tracks the design for a .into()-based ergonomics layer over this pattern.