#Error
Status: standard library specification draft.
This chapter defines the part of std.error that lib/std writes in
ordinary hd over the language tier:
- the inherent methods
chain,root_cause, andfindof&dyn Error, which list and search an error's sources; - the type
ErrorReportand the helperreport_of, which snapshot an error's message and its sources' messages; - the function
message, which makes an error from a text; - the method
contextofResult[T, E], which wraps errors with a message; - which std error types implement
Error.
The language tier keeps what the compiler knows by name:
| Item | Why it stays in the language tier |
|---|---|
the trait Error, its member source, and the impl dyn Error: block that holds chain, root_cause, and find |
Error Trait; the compiler generates implementations of it |
@error, @from, @source, and transparent errors |
Error Derivation, a compiler intrinsic that defines source (Error Causes) |
the erased &dyn Error and ? into it |
Erased Errors and Propagation |
what an entry point prints for an .Err |
Entry Results, which the host follows |
| which values cross a registered boundary | Boundary-Safe Values |
#Cause Chain
chain lists an error, then its source, then that source's source. It is an
inherent method of &dyn Error:
use std.error.Error
@error("disk full")
struct DiskError:
free: i64
@error
enum SaveError:
@error("cannot save $path")
Write(path: string, @source error: DiskError)
fn messages(failure: SaveError) -> List[string]:
let error: *dyn Error = &failure
error.chain().map(fn(error: &dyn Error) -> string: error.to_string())For SaveError.Write("notes.txt", DiskError { free: 0 }), messages
returns ["cannot save notes.txt", "disk full"].
- std-error.chain.reference-method
std.errordeclarespub fn chain(*self) -> List[*dyn Error]in itsimpl dyn Error:block, soe.chain()lists the causes of an&dyn Errorreferencee. - std-error.chain.method-only
std.errorexports no freechainfunction. - std-error.chain.source-order
error.chain()returnserrorfirst, then the error that itssourcereturns, then that error's source, and so on. - std-error.chain.source-endThe list ends with the first error whose
sourcereturns.None.
Note. The list is never empty. A transparent error's
sourceskips its member, asannot.error.transparent.sourcestates, sochainnever lists that member itself.
Note.
chainstops only at.None. A hand-writtensourcethat leads back to an earlier error makeschainrun forever.
Why. Entry Results prints an error and then each source that
chainyields after it. One list that starts with the error itself serves both that rule and code that walks the sources.
#Root Cause And Find
The &dyn Error methods root_cause and find walk the list that chain
returns:
use std.error.Error
@error("disk full")
struct DiskError:
free: i64
@error
enum SaveError:
@error("cannot save $path")
Write(path: string, @source error: DiskError)
fn innermost(failure: *dyn Error) -> string:
failure.root_cause().to_string()
fn free_space(failure: *dyn Error) -> i64?:
match failure.find::[DiskError]():
.Some(disk) => .Some(disk.free)
.None => .NoneFor SaveError.Write("notes.txt", DiskError { free: 0 }), innermost
returns "disk full", and free_space returns .Some(0).
- std-error.root-cause.method-chain
e.root_cause()returns the last shared error reference ofe.chain(), includingeitself when it has no cause. - std-error.dyn-reference-receiver.reference-values
chain,root_cause, andfindare inherent methods of&dyn Error, bytrait.error.dyn-methods.reference-values. Their receivers have type&dyn Error; a concrete error reference converts before the call. - std-error.find.method-chain-first
e.find::[T]()returns.Someof the first error ofe.chain(), in order, whose recorded type isT, and.Nonewhen no error does. - std-error.find.copied-valueThe found error is copied through the reference returned by
downcast_ref::[T](). - std-error.find.struct-copyFor a struct target, the returned value is an independent copy of that struct.
- std-error.find.concrete-exactThe target is a concrete error type and matches exactly, without a supertrait search.
- std-error.find.bound
Tmust implementError, so a target such asi32is an error. Error:unsatisfied-trait-bound.
use std.error.Error
fn code(failure: *dyn Error) -> i32?:
failure.find::[i32]() # error: unsatisfied-trait-boundWhy. Every error in a chain implements
Error, so a target that does not could never match. The bound turns that mistake into a compile error, as Rust'sdowncast_refbound onErrordoes.
#Error Reports
An ErrorReport is a boundary-safe snapshot of an error's message and the
messages of its sources:
use std.error.{ErrorReport, report_of}
@error("disk full")
struct DiskError:
free: i64
@error
enum SyncError:
@error("sync failed")
Save(@source error: DiskError)
fn summary() -> string:
report := report_of(SyncError.Save(DiskError { free: 0 }))
report.to_string() # "sync failed\nsource: disk full"- std-error.report.sources-struct
std.errordeclares the structErrorReport, a value with the public fieldsmessage: stringandsources: List[string], and no other fields. Copying a report copies both fields. - std-error.report.importCode imports both names, as in
use std.error.{ErrorReport, report_of}. - std-error.report.boundary-safeBoth fields are boundary-safe, so
ErrorReportis boundary-safe bymodule.boundary.allowed. - std-error.report.of.reference-values
std.errordeclarespub fn report_of(error: &dyn Error) -> ErrorReport. - std-error.report.messageThe report's
messageis theDisplaytext oferror. - std-error.report.source-textsIts
sourcesholds theDisplaytext of each successive source, starting witherror.source(). - std-error.report.display-sources
ErrorReportimplementsDisplay. Its text ismessage, then, for each element ofsources, a line feed,source:, and that element. - std-error.report.entry-textSo for an error
ewhose type implementsError,report_of(e).to_string()holds the lines that Entry Results prints for.Err(e), joined by line feeds.
Note. A report holds only text. It keeps no error value, so code cannot downcast it back to the error it came from.
Why. An erased
&dyn Errornever crosses a registered boundary (module.boundary.erased-error). A report keeps the message and the sources apart, so a receiver can show the first line alone or the whole chain.
#One-Off Errors
message makes an error from a text, as Go's errors.New and Rust's
anyhow! do, so a script that wants to fail needs no error type:
use std.error.{Error, message}
fn parse_port(text: string) -> Result[i32, *dyn Error]:
if text == "":
return .Err(message("empty port"))
.Ok(80)For parse_port(""), the error displays as empty port.
- std-error.message.reference
std.errordeclarespub fn message(text: string) -> *dyn Error. Code imports it, as inuse std.error.message. - std-error.message.reference-structThe returned reference points to a private struct, whose type code cannot name or match on.
- std-error.message.displayThe error displays as
text, unchanged. - std-error.message.sourceIts
sourcereturns.None, sochainlists only that error. - std-error.message.reference-result
.Err(message(text))is a valid result of a function returningResult[T, *dyn Error].
Why. An ad hoc failure otherwise needs a struct, a
Displayimpl, and anErrorimpl. The text is the whole error, as in Go.
#Error Context
context adds a message to an error on its way up, as Rust's
anyhow::Context does:
use std.error.{Error, report_of}
@error("not found: $path")
struct NotFound:
path: string
fn read(path: string) -> Result[string, NotFound]:
.Err(NotFound { path: path })
fn load() -> Result[string, *dyn Error]:
text := read("app.toml").context("loading config")?
.Ok(text)
fn summary() -> string:
match load():
.Ok(text) => text
.Err(failure) => report_of(failure).to_string() # "loading config\nsource: not found: app.toml"- std-error.context.reference-result
std.errordeclares the inherent methodpub fn context(self, message: string) -> Result[T, *dyn Error]onResult[T, E]for everyE < Error. - std-error.context.no-useCalling
contextneeds nouse, bytrait.own.inherent.std.no-use. - std-error.context.ok
.Ok(value).context(message)returns.Ok(value). - std-error.context.erased-wrapper
.Err(error).context(message)returns.Errof a shared reference to a wrapper containing&errorand that message. - std-error.context.private-wrapperThe wrapper is a private struct with private fields, whose type code outside
std.errorcannot name. - std-error.context.wrapper-traitsThe wrapper implements
DisplayandError. - std-error.context.wrapper-displayThe wrapper displays as its message.
- std-error.context.reference-sourceIts
sourcereturns.Someof the shared reference to the wrapped error. - std-error.context.method-chain
error.chain()lists the wrapper first, then the wrapped error and its causes. - std-error.context.report-sources
report_ofgives the context message, then onesource:line per source.
Why. Code that propagates with
?often knows what it was doing but not why the call failed.contextrecords the first, keeps the second as the source, and leaveschainandreport_ofunchanged.
#Standard Error Types
Every std error type implements Error, so ? and context accept it:
use std.error.Error
fn count(text: string) -> Result[i32, *dyn Error]:
value := text.parse::[i32]()?
.Ok(value)
fn width(text: string) -> Result[i32, *dyn Error]:
text.parse::[i32]().context("reading the width")| Type | Module |
|---|---|
ParseNumberError |
std.num |
Utf8Error |
std.text |
ParseBoolError |
std.text |
ParseCharError |
std.text |
ConsoleError |
std.console |
DecodeError |
std.encoding |
FsError |
std.fs |
ProcessError |
std.process |
JsonError |
std.json |
ParseTimeError |
std.time |
CliError |
std.cli |
HostError |
std.host |
RegexError |
std.regex |
NetError |
std.net |
- std-error.std-types.impl.reference-valuesEach type in the table above implements
Error. So?converts it into aResult[T, *dyn Error], bytrait.error.result.reference-values, andcontextaccepts it. - std-error.std-types.sourceTheir
sourceis the default.None. - std-error.std-types.displayEach keeps the
Displaytext its chapter defines. - std-error.std-types.string-no-implThe language type
stringdoes not implementError. - std-error.std-types.string-messageA text error is constructed explicitly with
message(text). - std-error.std-types.newA std error type added later implements
Errortoo, and joins this table.
Why. An application error type is the erased
&dyn Errorin most scripts. A std error that cannot reach it forces a wrapper type at every call, which Rust'sstd::io::Errorand Go'serrornever need.
See also: Error Trait, Error Derivation, Entry Results, Result.