component-model
component-model copied to clipboard
Consider adding an `error` type
I think it would be beneficial to add a built-in error
type to the Component Model and WIT to serve as the go-to type to use when you want to propagate rich error information for the benefit of debugging. With this type, result<T, error>
would become the common return type of fallible functions.
Just as a high-level sketch to paint a picture:
-
error
could have (immutable) value semantics but be represented in core wasm as ani32
index into an error table managed by the runtime (like resources/handles), allowing it to be efficiently propagated between components without the usual linear-memory copy of normal values. -
error
values could contain a boxed heterogeneous value payload that is supplied by wasm when creating anerror
(via some newerror.new
canon built-in) and can be conditionally extracted by wasm (via some other newcanon.payload
built-in) if the payload type dynamically matches. -
error
values could also contain context information (incl. a callstack) that would be automatically filled in by the host atcanon.new
and could be repeatedly extended (by creating newerror
values from the oldererror
values) with additional context when propagating anerror
. -
error
values could be logged directly (via theiri32
index passed to another new canon built-in) such that the host could easily do a nice job rendering the context for the developer (analogous to how browser consoles nicely render the stacks of uncaughtError
objects)
Some potential benefits of having error
be built-in include:
- Bindings generators would have the additional semantic information to bind
error
to the languages' idiomatic error-with-context constructs (e.g., a JSError
object or Rustanyhow::Error
) - The amount of context captured could be configured in the wasm runtime by the developer (independently of the code being executed), allowing full expensive context to be captured when useful (debugging) and cheap or even zero context to be captured in high-volume scenarios where performance or cost are being optimized.
- Hosts could offer a "log every time an
error
is created" runtime option to help debug cases where errors are swallowed or handled incorrectly (similar to first-chance exceptions). - wasi-io
error
and all associated payload-accessor functions (likehttp-error-code
) could be removed. These are currently a source of anti-modular coupling between the implementations of unrelated WASI interfaces, with the net effect being that if you want to virtualize just one WASI interface that uses wasi-io, you end up being forced to virtualize/wrap them all.
Additionally, for the same reason that wasi-io's input-stream
and output-stream
want to use a single wasi-io-defined, payload-agnostic error
resource type (instead of having each distinct WASI package define its own stream type with its own domain-specific error variant), I think Preview 3 async depends on there being a single C-M-level error
type (which you get when reading a stream and an error occurs). So if nothing else, I'd like to consider adding error
in a Preview 3 timeframe, but if anyone was keen to work on this earlier, we could work on just error
earlier.
A few high-level open questions:
- Once we have the "log an
error
" canon built-in mentioned above, we might want it to just be a general structured logging function (that happens to takeerror
s). I think there'd be a lot of benefits to this too, but it does increase the problem scope... but maybe not too much? Alternatively, we could cut the other way and hold off on the "log anerror
" built-in (initially). - We could add a bit more static type information about the error payload via optional generic parameters:
error<P>
andstream<T,P>
whereP
was the error payload type, butP
was ignorable via subtyping rules sayingforall P. error<P> <: error
and similarlyforall P. stream<T,P> <: stream<T>
(noting that error payload values are not lost in these subtypings; just the static knowledge of their type). This would provide better static typing in common cases where you're directly consuming the result of, e.g., wasi-httpresource body { consume: func() -> result<stream<u8, error-code>>; }
. I'm not sure if it's worth the hassle to add this though and we could always add it backwards-compatibly later, so I'm inclined to leave it out (initially).
There's a lot of details left to figure out (and maybe the basic sketch isn't right either), but I thought I'd file this now to collect thoughts and use cases.