I'm trying to pass a Rust struct (exported) to a javascript class (imported) but I'm running into the message: "interface transitively depends on an interface in incompatible ways". Is it possible to do this?
You're hitting this error which more-or-less means that the imports/exports into the world aren't resolvable, and you probably need to export more things.
For example if interface A depends on B depends on C and you export only A and C the fix is to also export B
(sorry it's known the error message is particularly bad in this case)
So in this example, is the issue that wit-bindgen wants interface b
exported? I want interface b
to be extended in JS and passed to Rust, but also pass a separate Rust export:
package test:pack;
interface a {
use b.{factory};
resource bbuilder {
constructor();
register-bfactory: func(bfactory: factory);
build: func();
}
}
interface b {
use c.{c};
resource b {
b: func() -> string;
}
resource factory {
b: func(c: c) -> b;
}
}
interface c {
resource c {
c: func();
}
}
world pack {
import b;
export a;
export c;
}
yes you'll need to export b in addition to a/c
and that'll fix that error
So the issue, is that when I export b, but need to send a JS extended b.Factory by extending an external JS implementation by calling jco with --map
, then register-bfactory errors due to my extended b.Factory Not a valid "Factory" resource.
import { a, c } from "/out_dir/jcotest.js";
import { b } from "/packb.js";
class B extends b.B {
b() {}
}
class Bfactory extends b.Factory {
constructor() {
super();
}
b(c) {
console.log("Creating B");
var out = c.c();
console.log("C - ", out);
return new B();
}
}
var bFactory = new Bfactory();
var bBuilder = new a.Bbuilder();
bBuilder.registerBfactory(bFactory);
console.log("registered factories");
bBuilder.build();
It seems like it's looking for an instance of b.Factory that was exported, and defined in the generated jcotest.js, not the imported b.Factory defined in packb.js
for that I might tag in @Guy Bedford who might know the JS/jco bits better than I
And to add more context, this is related to the other question I raised that @Guy Bedford merged a resolution for. In my case, I would also be happy to export all three interfaces and merely extend an exported type (b.Factory), but I also seep the not a resource error there.
@Jeremy Slater this should work - the check that is being done by the function is just a arg instanceof Factory
, which will be true for subclasses. So the issue is that you must export Factory
to the component to import to begin with I think, perhaps it's undefined or something? The component must be able to import the base class itself (not an instance) to be able to do the instanceof
check.
I'm not seeing a simple instanceof in the codegen, it's looking up the resourceHandleSymbol:
var rep3 = arg1[resourceHandleSymbol];
if (rep3=== null) {
throw new Error('Resource error: "Factory" lifetime expired.');
}
if (rep3=== undefined) {
throw new Error('Resource error: Not a valid "Factory" resource.');
}
This seems like an effect because i'm exporting and importing and it's expecting it to be a wasm compiled Rust struct?
In fact if it just shuffle the wit around and remove c
from being consumed by b
, then I can get away without the export of b
and yes in fact the codegen does a simple arg1 instanceof Factory
.
@Jeremy Slater the imported Bfactory and exported Bfactory resources are completely different resource types, and are not interchangeable - if a function expects the exported resource type, you must pass the exported resource type not the imported resource type
Sure, that's what I figured was the issue, so this leaves two questions; if I import and export b
, can I specify that I want to use the imported b
and not the exported b
? If not, then can I not export b
but still pass an exported resource to the imported b
?
if I import and export b, can I specify that I want to use the imported b and not the exported b
The way to do this in WIT I believe is via use imported-interface.{foo as bar};
to disambiguate them by name even though they have the same name. Also https://github.com/WebAssembly/component-model/pull/308 may be related here.
I'm not sure that even that PR inherently addresses what seems to be my issue. I think I'm having trouble explaining the issue. Basically wit has a problem if an exported func takes an argument of an imported resource that has a func taking an argument of an exported resource. wit can handle either direction of dependencies, exported with imported resource or exported with imported resource, but once it's a chain of export - import - export it breaks. It doesn't appear that import use is going to resolve this chaining problem. In my case I don't actually want to export b
, it's entirely imported.
Possibly related: https://github.com/WebAssembly/component-model/issues/272
Thanks @Joel Dice for pointing that out, it certainly seems like that's related, although I can always have an export use an import and an import use an export, just not an export using an import using an export. I can loophole around this by getting the export on the js side and passing it to my js imported object like this:
import { interfacea, interfacec } from "/out_dir/jcotest.js";
import { interfaceb } from "/packb.js";
class Resourceb extends interfaceb.Resourceb {
constructor(resourcec) {
super();
this.resourcec = resourcec;
}
funcb() {
var result = this.resourcec.funcc();
console.log("funcb - ", result);
return result;
}
}
class Factoryb extends interfaceb.Factoryb {
create() {
console.log("Creating B");
var resourcec = builderb.resourcec();
return new Resourceb(resourcec);
}
}
var factoryb = new Factoryb();
var builderb = new interfacea.Buildera();
builderb.registerFactoryb(factoryb);
console.log("registered factories");
var bb = builderb.build();
This definitely has some code smell, but am I really hitting a limit of wit-bindgen and therefor this is the (only) way to do this?
Last updated: Dec 23 2024 at 12:05 UTC