kaivol opened issue #8562:
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 callingcall_init
returns aResourceAny
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.
alexcrichton commented on issue #8562:
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 typeT
is only valid for a single instantiation of a component. Each instantiation should get a unique typeT
, 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.
alexcrichton added the wasm-proposal:component-model label to Issue #8562.
alexcrichton added the wasmtime:api label to Issue #8562.
FrankReh commented on issue #8562:
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 twooperator
's the same type? Separate instances of the same type?
kaivol commented on issue #8562:
@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).
kaivol commented on issue #8562:
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 twooperator
'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).
FrankReh commented on issue #8562:
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.
alexcrichton commented on issue #8562:
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.
FrankReh commented on issue #8562:
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.)
FrankReh edited a comment on issue #8562:
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.
FrankReh edited a comment on issue #8562:
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.
alexcrichton commented on issue #8562:
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!
Last updated: Dec 23 2024 at 12:05 UTC