WebAssembly / component-model

Repository for design and specification of the Component Model
Other
981 stars 82 forks source link

Consider adding an `error` type #389

Open lukewagner opened 3 months ago

lukewagner commented 3 months ago

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:

Some potential benefits of having error be built-in include:

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:

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.

badeend commented 3 months ago

All in all: this seems sensible to me. The concept of "errors" are definitely ubiquitous enough to warrant special casing in the component model.


With this type, result<T, error> would become the common return type of fallible functions.

If you were to translate this to today's WIT syntax, does this mean that result<A> effectively becomes result<A, error>, and result<A, B> becomes result<A, error<B>>?


error values could contain a boxed heterogeneous value payload that is supplied by wasm when creating an error (via some new error.new canon built-in) and can be conditionally extracted by wasm (via some other new canon.payload built-in) if the payload type dynamically matches.

Makes sense. Does the payload field behavior need to be special-cased for errors, though? Sounds like you're only one step away from describing a general purpose any type (as found in e.g. Rust).

lukewagner commented 2 months ago

If you were to translate this to today's WIT syntax, does this mean that result effectively becomes result<A, error>, and result<A, B> becomes result<A, error<B>>?

I'm not proposing to change the meaning of result<A> -- that would continue to mean "the error case contains no payload" -- but if what you're asking is whether anyone would ever continue to use result<A> (vs. result<A, error>), I think there may be cases where result<A> still makes sense: when the failure case is trivial or uninteresting, error might feel like overkill. (If we find that in practice we almost always want the error case of result to have an error and thus we want to change result<T,E?> to mean variant { ok(T), %error(error<E?>) }, then we'd need to find a backwards-compatible plan to transition Preview 2 code, which will be tricky, so I'd like to wait and see if that is indeed the case.)

Does the payload field behavior need to be special-cased for errors, though? Sounds like you're only one step away from describing a general purpose any type (as found in e.g. Rust).

I think error payloads can only include pure (stateless, identity-less) values, which rules out handles, futures, streams and buffers and thus it wouldn't be a true "any". Adding a general-purpose any type would have a number of subtle and tricky design questions so I'd ideally like to hold off on doing that until necessary (which, after we add WIT templates, may be never?).

badeend commented 2 months ago

I think error payloads can only include pure (stateless, identity-less) values, which rules out handles, futures, streams and buffers

Okay👍. Out of curiosity: why's that?

lukewagner commented 2 months ago

If error payloads can contain handles or other impure values, then we'd have to worry about all the lifetime/ownership rules for error, which would be even harder than normal since the payload type has been erased. OTOH, if error can only contain pure values, all these lifetime issues go away and the implementation can just be simple ref-counting (noting that pure values and contexts are acyclic).