bytecodealliance / wasmtime

A fast and secure runtime for WebAssembly

Home Page:https://wasmtime.dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add static type information to exported resources

kaivol opened this issue · comments

Problem

I use the following .wit to describe a WASM component which provides a long living operator that is initialized once and then invoked multiple times to perform some operation:

world example {
    export exports: interface {
        resource operator {
            init: static func(...) -> operator;
            run: func(...) -> ...;
        }
    }
}

Using this component from a wasmtime host is somewhat unergonomic, because calling call_init
returns a ResourceAny instance, which doesn't carry any (static) type information.
Calling methods on this resource is thus very verbose (see below), and there is a risk of mixing up resources of different types.

let resource = component.exports().operator().call_init(&mut store, ...)
component.exports().operator().call_run(&mut store, resource, ...)

Feature

Ideally, the rust-representation of the resource returned from a WASM component would contain sufficient type information to allow its (and only its) methods to be called with standard method syntax.

Benefit

Better ergonomics when working with resources (reduced error potential, less verbose syntax).

Implementation

It feels like this should not be too complicated, but maybe I'm missing something and what I'm suggesting isn't actually possible.

Thanks for the report! This is currently an intentional design decision but not because we think it's a good idea, moreso we couldn't think of a better idea at the time.

One tricky part here is that Rust's type system cannot statically rule out all errors here. For example if the exported resource had type T then that type T is only valid for a single instantiation of a component. Each instantiation should get a unique type T, but that can't be reflected in Rust's type system.

Not to say that prevents improving on the current situation of course. I think it'd be great if the Resource<T> approach could be fit in for more, albeit not ironclad, safety here.

Each instantiation should get a unique type T, but that can't be reflected in Rust's type system.

I only ask because this seems a learning opportunity for people new to this technology.

Why should that be the case? If call_init is called twice, aren't the two operator's the same type? Separate instances of the same type?

@alexcrichton Thanks for the reply, it's always interesting to hear about the rationale / reason behind such design decisions!
Feel free to close this if you consider the issue to be resolved (at least for the time being).

Each instantiation should get a unique type T, but that can't be reflected in Rust's type system.

I only ask because this seems a learning opportunity for people new to this technology.

Why should that be the case? If call_init is called twice, aren't the two operator's the same type? Separate instances of the same type?

As I understand it, they are (only) of the same type in the context of the component's .wit defintion, but if they originate from different instantiations of the component they are nevertheless generally not compatible (although that should not be a problem if it is only about calling instance methods on them (?)).
When alex says Each instantiation he's talking about instantiations of the component, not instantiations of the exported resource (correct me if I am wrong).

Thank you. You are right that I didn't take the use of instantiation correctly. I still have questions about why this part of the API doesn't use the Rust type system more directly, but I won't hijack this thread further.

Yeah @kaivol is correct. If you have a resource A which exports a resource R, then if you instantiate the component twice producing A1 and A2 the R resource has a different type between A1/A2. Effectively the resource type has a bit of runtime state which indicates which component it came from. This is the part that I don't know how to reflect into the Rust type system statically.

I'm hesitant to close this though because it would be possible to create a type representing R which ensures that you're using "some R" with "some A". There'd still be a runtime check that the two match but that's probably a better situation than today where there's no guard rails whatsoever at compile time.

the R resource has a different type between A1/A2

Is it really a different type, or is it up to the implementation to keep track of which A component the R is tied to?

As an example, the host could keep a weak pointer to the component instance the R came from, and either the methods the host is trying to call use that inner weak pointer as the target, or it's used for a runtime comparison with the target.

(Just wanting to make clear what causes my confusion about the status quo. I feel very out of my depth asking this kind of question but all of the builders of this technology are being so helpful.)

I agree that doesn't solve the problem statically. The compiler won't tell you if you match a component instance with the wrong resource.


There are crates like generativity and async-local, which maybe try to solve the runtime instantiation vs compile time compatibility type problem but I'll admit I've never fully understood how to use them in anything but the simplest examples plus they don't support all architectures of interest plus the latter is riddled with unsafe calls so I'm in no way wanting to sound like an advocate but it does seem some projects are using them successfully.

Oh no you're right, the implementation keeps track of which types come from which instance and mistakes are detected dynamically. I was mostly reflecting on how I don't think there's a way in Rust to remove this dynamic check and reflect everything statically. I haven't looked into those crates though, myself, so I'll try to take a look!