Skip to content

Are multiple identical imports with different aliases possible / a goal? #526

Closed

Description

@meastp

I came across the use case in the wasmCloud runtime linking docs.

Excerpt:

For example, a component may use the wasi:keyvalue interface to link to both a local key-value cache for ephemeral storage and a cloud-based store for more resilient storage.
In this example, you're using the same key-value interface and the same operations, but the interface needs to be configured differently. These different configurations of the interface can be differentiated with a link name.

Wouldn't this be better standardized as native component model functionality, avoiding different vendor solutions?
Perhaps by giving imports names or aliases somehow? One must be able to determine which storage import (local or cloud-based) is used in the language bindings/source code.

Using the LEGO brick terminology which is popular for illustrating wasm component imports/exports: when giving imports names or aliases, each aliased import get its own tube/socket that must be filled separately.

Did this make sense? Perhaps it could be done another way, but I think solving the use case in wit/wasm components is reasonable..?

Activity

  1. lukewagner commented on Jun 4, 2025

    @lukewagner
    Member

    Hi, great question, and agreed! IIUC, the root issue you're rasising is that it's only possible to import a given interface name (like wasi:keyvalue/store) once in a given world (due to the requirements of name-uniqueness). This was raised earlier in #287 and my current best proposal on the WIT- and WAT-level generalization to support this use case is in #287 (comment). I think this is a practical necessity so I'd like to push to include it before declaring a 1.0-rc. If anyone wants to work on tooling support for this, I'm happy to work up a PR to support.

  2. meastp-nk commented on Jun 5, 2025

    @meastp-nk

    @lukewagner thanks - yes, that's the issue (and this can be closed as a duplicate- perhaps #287's title could be changed to reflect the use case)

    One thing I'd really like to keep with this feature, is how targeting wasm+wasi for e.g. a .NET project will transparently use the wasi interfaces and not require code changes.

    Assume a project today use two volumes/disks: one local temp disk and another remote cloud disk. They both would be rewritten by the .NET compiler(i think?) to use wasi:filesystem, there is only one import in the component so one can not easily have two different disks.

    For this use case to work in the same automatic way when targeting wasm, some annotation in the source code is required to point to the right name (one or two in your example). .NET/c# could for example require a wasm/wit-specific attribute with the name.

    I just hope naming interfaces (when it is added to the component model) will (still) make it easy to target wasm with no or minimal source code changes. Ideally, any source code changes should be annotations and not affect non-wasm builds.

  3. lukewagner commented on Jun 5, 2025

    @lukewagner
    Member

    perhaps #287 title could be changed to reflect the use case)

    Yes, good idea, done.

    They both would be rewritten by the .NET compiler(i think?) to use wasi:filesystem, there is only one import in the component so one can not easily have two different disks.

    Filesystems are a somewhat special case since, as you said, they are used ubiquitously and there is an ambient assumption that there is only one. In your example where there are two disk imports, probably the best way to achieve source compatibility is to virtualize a single filesystem with the two disks mounted at different directories using a tool like wasi-virt (which can't do exactly this today, but I think should be extended over time so that many different kinds of WASI interfaces can be "mounted").

  4. meastp commented on Jun 6, 2025

    @meastp
    Author

    @lukewagner Yes, you're right, that's more natural for filesystem.

    I tried to find a use case where annotating with the alias/name in source code would be helpful, but so far I can't think of any.

    Perhaps it is only used when linking /composing components?

  5. lukewagner commented on Jun 6, 2025

    @lukewagner
    Member

    Going back to the kv-store example, where you have:

    world w {
      import redis: wasi:keyvalue/store;
      import cosmos: wasi:keyvalue/store;
     ...
    }

    it seems useful to have redis.open("my-bucket") vs. cosmos.open("other-bucket") where redis and cosmos are generated bindings and validated to exist by statically-typed languages.

  6. cfallin commented on Feb 25, 2026

    @cfallin

    I'm finding this and companion issue #287 after recently thinking about component composition and was spurred to write up some thoughts after seeing your presentation today, @lukewagner (nice job!).

    In summary I think this is a really important issue to solve for generality and compositionality, and I'm finding the "case-by-case thinking" in replies above to miss some of the fundamental PL expressivity aspects (IMHO) -- it might be worth making a few analogies to help think about this.

    First: I think of a component with imports/exports as a staged function: a module M that imports I and exports E is a module-level ("type-level") function M: I -> E, and composition is easier to think about when one sees it as such IMHO. Or in a dependency-injection framing, we take one parameter of type I. Making these imports rather than runtime parameters is actually an orthogonal axis of static monomorphization vs. dynamic (runtime) dispatch. Seen that way, taking two Is rather than one seems a totally natural desire to me.

    So from that point of view, when desiring M: (I, I) -> E, suggesting a dynamic dispatch/demultiplexer (the VFS example above) pushes the problem down without solving it: how does the VFS/demultiplexer component link to the distinct filesystem components? In other words, start from a big component marketplace that has fs1 that imports disk and exports filesystem, and likewise fs2; something needs to import two filesystems to tie them together. The current static limitation is somewhat like C++'s variant or tuple type that can take only one field of a given type; one would need to newtype filesystem to filesystem1 or filesystem2 at the type (interface) level to compose in today's WIT. One could build tooling to do that, but that's awkward; it creates a shadow type-composition system on top of the actual type system. The VFS composition example is fundamentally not possible without this, I think; it's not just a matter of efficiency (dynamic dispatch).

    But second, on dynamic dispatch: I find the suggestion to resort to dynamic dispatch (the VFS example with mountpoints / subpaths) a bit puzzling since so much of the component model otherwise is focused on enabling static dispatch and linking: the dynamic choice has to be made in every runtime path (not just file-open).

    Finally, I think that focusing on this as a way to make config strings (or VFS mountpoints, or ...) static and statically analyzable is missing the deeper expressivity hole here: it's really about being able to link two different implementations (completely separate components), not about naming e.g. two different endpoints. Because calls are ultimately statically resolved (the CM doesn't have vtables), that fundamentally means this is needed anywhere there is more than one backend impl.

    I totally see why WIT exists today as it does, by the way, by virtue of being co-evolved with WASI interfaces: it's natural to segment WASI into "sections" and call each an interface, and that framing leads to "there's just one filesystem in a given world" rather than "I may want 2 filesystems" etc.

  7. tschneidereit commented on Feb 25, 2026

    @tschneidereit
    Member

    Finally, I think that focusing on this as a way to make config strings (or VFS mountpoints, or ...) static and statically analyzable is missing the deeper expressivity hole here: it's really about being able to link two different implementations (completely separate components), not about naming e.g. two different endpoints. Because calls are ultimately statically resolved (the CM doesn't have vtables), that fundamentally means this is needed anywhere there is more than one backend impl.

    I agree this is important, and think it can be fully realized in terms of the parametrization approach that's also useful for static config analyzability. Since CM interfaces are structurally typed, it's already possible to wire up one component's import of wasi:fs to another component's export of my-custom-virtual:fs. That's e.g. part of what I'm using for my environment isolation proposal for Spin.

    If the Component Model gains support for parametrization of imports, the dynamically dispatching bindings discussed are only one way for a host to make use of the parameter(s). We should additionally be able to add an API to (e.g.) Wasmtime to register different implementations of wasi:fs based on the import parameter, without any dynamic dispatch overhead and complexity. During pre-instantiation/linking, the parameter would be used to look up the implementation to use and link up with the import in question.

    I additionally think that this is the right approach, where the component can't say "I need this specific implementation of wasi:fs, and instead all it can do is attach some metadata to its request of the interface. The host can then decide how to satisfy the request however it sees fit, as long as the guest-visible semantics are upheld. (I'm sure we're in agreement on this, to be clear!)

  8. lukewagner commented on Feb 25, 2026

    @lukewagner
    Member

    Thanks for filing your thoughts @cfallin! This is indeed a subtle problem space and it's possible we're thinking about different problems and it's also possible that the hypothetical syntax I showed is confusing people into thinking that the feature we're discussing in #287 is more sophisticated than it really is, because really all we're talking about here is relaxing the grammar for importname and exportname.

    So just as background: today I can write:

    world w {
      import foo: interface { ... }
      import bar: interface { ... }
      import ns:pkg/some-interface;
      import ns:pkg/some-other-interface;
    }

    and the names you see in the raw component type are:

    (component
      (import "foo" (instance ... ))
      (import "bar" (instance ... ))
      (import "ns:pkg/some-interface" (instance ... ))
      (import "ns:pkg/some-other-interface" (instance ...)))
    )

    But I can't write:

    world w {
      import foo: interface { ... }
      import foo: interface { ... }
    }

    or

    world w {
      import ns:pkg/some-interface;
      import ns:pkg/some-interface;
    }

    because these would produce two imports with the same names and component types have a name uniqueness requirement.

    So let's say I want one component to import two different implementations of ns:pkg/some-interface. Now what I could do is use two different plainnames:

    world w {
      import foo: interface { ... contents of ns:pkg/some-interface ... };
      import bar: interface { ... contents of ns:pkg/some-interface ... };
    }

    and what's good about this approach is that it preserves all the declarativity and static linkage of the component model which is great for all the reasons @cfallin mentions and I think we all agree on and want. No virtual dispatch here.

    But the downside is that we've had to copy-paste ns:pkg/some-interface. Of course we could do something like allow you to write interface { include ns:pkg/some-interface; } to avoid the literal copy-paste, but that's still unsatisfactory b/c we've lost the declarative semantics that you get from having "ns:pkg/some-interface" in the import name. And semantic information is useful b/c automated tooling can see ns:pkg/some-interface and do helpful things (like help me find a component that exports ns:pkg/some-interface).

    Thus, for this scenario, the idea is to be able to write:

    world w {
      import foo: ns:pkg/some-interface;
      import bar: ns:pkg/some-interface;
    }

    and have both the foo and ns:pkg/some-interface strings get predictably mangled into a single name string, e.g. using the probably-could-be-improved idea in #287:

    (component
      (import "[implements=<ns:pkg/some-interface>]foo" (instance ...))
      (import "[implements=<ns:pkg/some-interface>]bar" (instance ...))
    )

    and now we've satisfied name-uniqueness, we've preserved declarative linking, but we've also preserved the semantic intent of ns:pkg/some-interface. When generating guest bindings for this world w, just the foo/bar kebab-case names would be used to generate a foo/bar module/namespace to wrap the two sets of some-interface types/functions and thus probably we should tweak the strong uniqueness requirement to additionally require that the plainname parts of all import names be unique.

    So that's the baseline feature here. There's also an add-on idea to allow an additional string "payload" that doesn't fit in a kebab-case name that you'd otherwise have to stick in a custom section, but it still just goes into the name, so it's the same story. So maybe I'll stop here and see if this framing of the problem/solution makes sense?

  9. cfallin commented on Feb 25, 2026

    @cfallin

    Yes, all of that makes sense!

    My main goal above was more-or-less to "vote in support" of this from a fundamental expressivity point of view: I wanted to note that the proposed workarounds above in this issue actually can't work for e.g. the VFS case, and so we really do need it. I don't have any opinions on the specifics of name-mangling design as long as the case is supported. Thanks!

  10. lukewagner commented on Feb 25, 2026

    @lukewagner
    Member

    Oh hah, I misunderstood the beginning of your comment; that explains why I was agreeing with so much of what you said :)

  11. lukewagner commented on Jul 30, 2026

    @lukewagner
    Member

    Fixed by #613

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions