Hello everyone,
I'm trying to understand the entities of the component model with experimenting while wit-bindgen
for Rust. Obviously the component model is a new concept with limited analogies to OOP and also visual representation using UML is unfamiliar. This post however is about the "meta model" behind the CM.
Question Set 1
In Luke's presentation he's describing a component with several imported interfaces of the same kind and one exported (path-based-router). My understanding so far was that you cannot import or export an interface more than once.
But is this a limitation in the CM or in wit-bindgen
? Is it envisioned to become possible?
Question Set 2
Is it possible to relay an interface through a component? Or does the component have to re-implement the full interface? If it has to re-implement some of the interface - can it at least relay resources through (as in: the component may decide to hand out a resource implemented within it or relay a resource received from the import side)?
you cannot import or export an interface more than once [...] is this a limitation in the CM or in
wit-bindgen
This is a limitation of the WIT model which applies to wit-bindgen (and any other WIT-based tooling like wasmtime's bindgen)
Is it possible to relay an interface through a component?
A parent component can pass its own imported instance (implementing an interface) through to its child, which I think is what you're asking here.
If it has to re-implement some of the interface - can it at least relay resources through (as in: the component may decide to hand out a resource implemented within it or relay a resource received from the import side)?
This is a bit subtle; a resource's type is determined by the component instance that defines the resource. A parent component could import a resource type and pass instances of that resource through to a child or it could define a resource with the same shape and give that to the child, but it cannot mix the two options in a single implementation.
Also, if the parent component defines its own resource it must also implement its own version of any functions that use that resource since the resource type will no longer match any externally-defined type.
@Lann Martin which of these are current limitations and which are intended to remain?
I'm not sure - I wasn't involved in that design
You can duplicate interfaces by copy-pasting the definition multiple times - interface types are structural so this would have the same effect as "aliasing" an interface. I don't know if anyone has proposed a nicer way to do that yet.
The eventual goal is to extend WIT to match the expressivity and power of the component model, e.g. importing an interface multiple times under different names, so I'd phrase it as a current limitation
Thanks Alex -- the reason I ask is because often due to short responses (we're doing other things) we fail to convey that the way it is now is different from the way it is planned to end up. So it's important to say so. :-)
Oh of course and makes sense!
Thanks for your explanations.
I realize now that I also don't understand the implications of putting components side-by-side rather than composing (wrapping) them. Are there things you can/cannot express in one way or the other?
Lann Martin said:
Is it possible to relay an interface through a component?
A parent component can pass its own imported instance (implementing an interface) through to its child, which I think is what you're asking here.
What I meant to ask is: can a component pipe an imported interface through to an exported one (unconditionally, without modification, pure "routing") while NOT wrapping?
Lann Martin said:
If it has to re-implement some of the interface - can it at least relay resources through (as in: the component may decide to hand out a resource implemented within it or relay a resource received from the import side)?
This is a bit subtle; a resource's type is determined by the component instance that defines the resource. A parent component could import a resource type and pass instances of that resource through to a child or it could define a resource with the same shape and give that to the child, but it cannot mix the two options in a single implementation.
Again, I was thinking about having the two side-by-side at the same nesting level. The limitation probably is the same: "a resource's type is determined by the component instance that defines the resource". So not possible, right?
There isn't much difference between parent/child and "sibling" component relationships conceptually. Even when you are only concerned with two "siblings" there must always be some parent tying (composing) the two together.
can a component pipe an imported interface through to an exported one (unconditionally, without modification, pure "routing") while NOT Wrapping?
Yes
Lann Martin said:
You can duplicate interfaces by copy-pasting the definition multiple times - interface types are structural so this would have the same effect as "aliasing" an interface. I don't know if anyone has proposed a nicer way to do that yet.
Does it mean interfaces do not have to match strictly? So if interface A is a subset of B, an import of A could be satisfied from an export of B?
Alex Crichton said:
The eventual goal is to extend WIT to match the expressivity and power of the component model, e.g. importing an interface multiple times under different names, so I'd phrase it as a current limitation
Would below example .wat
be legal then?
(component
(import "handler1" (instance ... [export requirements]))
(import "handler2" (instance ... [same export requirements]))
...
)
Can we just stop putting (quoted) interface types after the import
keyword and use (internally?) unique interface names? (No semantic in that quoted thing?)
In face of the above two aspects, I wonder how wit-bindgen
(and similar tools) could make sure that http/types.incoming-request
is the same type in the target language irrespective of its origin (handler1
or handler2
interface instance). Of course I'm talking about strongly-typed languages here.
Say in C++ or Rust, we get distinct types handler1::IncomingRequest
and handler2::IncomingRequest
. Then we can't use them interchangeably, right?
To unify we'd have to use something like a Rust enum or C++ std::variant) and/or
apply static polymorphism like C++ concepts or extra Rust traits.
It appears this would add complexity and boilerplate code...
Does it mean interfaces do not have to match strictly? So if interface A is a subset of B, an import of A could be satisfied from an export of B?
At the interface level, yes: B may provide more imports than A requires, but all of the matching imports must (currently) be identical.
Would below example
.wat
be legal then?
Yes. You can also play around locally with various combinations using wasm-tools validate
too.
Can we just stop putting (quoted) interface types after the
import
keyword and use (internally?) unique interface names? (No semantic in that quoted thing?)
Sorry I'm not sure what you mean by this, can you expand?
Say in C++ or Rust, we get distinct types
handler1::IncomingRequest
andhandler2::IncomingRequest
. Then we can't use them interchangeably, right?
Currently the way you'd represent what you're saying in WIT is something like:
world foo {
import handler1: interface { /* copy of wasi:http */ }
import handler2: interface { /* copy of wasi:http */ }
}
so not great in the sense you have to copy everything. When doing this though, yes, separate types would be used in the guest language.
Let's assume there was a syntax in WIT to re-use the same interface definition for handler1
and handler2
. It helps DRY in WIT but would it be possible that wit-bindgen
could conclude to use the same set of constructs in the target language (resource types, handle types)?
In some sense this is all hypothetical where nothing is currently yet implemented so it's possible to do whatever we want when implementing it. In another sense though this may or may not be possible. For example if you import two instances in a component and both export a resource type, those resource types are distinct and not the same. That needs to be mapped to the language level as two distinct types.
If, however, both instances refer to the same resource type, then all the types are the same, and the types in the guest language could all be the same, yes.
This is why some WASI interfaces split the types into their own interface, for example wasi:http/types
. That way when you import wasi:http/outgoing-handler
it only refers to the types instead of redefining them. So for example if you imported two copies of wasi:http/outgoing-handler
they'd refer to all the same types, just different functions. If you imported two instances of wasi:http/types
, however, all the types would be duplicated.
Alex Crichton said:
This is why some WASI interfaces split the types into their own interface, for example
wasi:http/types
. That way when you importwasi:http/outgoing-handler
it only refers to the types instead of redefining them. So for example if you imported two copies ofwasi:http/outgoing-handler
they'd refer to all the same types, just different functions. If you imported two instances ofwasi:http/types
, however, all the types would be duplicated.
Yes so this way I could relay
say a resource type, through various components without redefining it. However if I want alternative implementation of the resource it immediately becomes a redefinition, an incompatible thing. That's rough.
Alex Crichton said:
For example if you import two instances in a component and both export a resource type, those resource types are distinct and not the same. That needs to be mapped to the language level as two distinct types.
This is unfortunate. Would it be a possible way forward for WASI to enable this? Say, by making resources be backed by not only a resource index but also an instance/interface index to route member function calls to the resource's home interface?
Alternatively maybe there could be a hint in WIT hinting to wit-bindgen
that even though resources are different (sourced via different interfaces) they can be polymorphic in the target language. Say, in Rust, there would be a trait generated (name hinted in WIT) which all the distinct-but-similar resource types implement. If this matches the trait which a Rust component needs to implement to export a resource we can trivially re-export an imported resource. It is sure not great in terms of efficiency though.
Sorry I fear I may not be understanding what it is you're trying to achieve. I'm trying to answer in terms of "this is what the spec says" and it looks like you're learning about the spec and asking questions along the lines of "can the spec model this" but I don't quite understand the "this" well enough. I fear I might be giving the wrong impression that things are or aren't allowed in the component model since components might support the higher-level goal you're trying to achieve if not in exactly the way you're thinking about modelling it.
Could you detail a bit more the higher-level of what you're doing?
Yeah, me too. It sounds as if you want to declare some types somewhere, and always reuse those types in each part of component conversations and never get "redefined" -- but like Alex, I'm not clear that's your objective. Or that that is a precise enough description.
Last updated: Dec 23 2024 at 12:05 UTC