Skip to content

Awkward lack of borrowing on variants that contain resources #615

@cfallin

Description

@cfallin

Recently in building up a WIT description for a debugger interface in Wasmtime (current draft, implemented and in use in a WIP branch), I have run into a somewhat awkward interaction between variants, resources, and borrowing.

Consider the problem of modeling a Wasm value in WIT: the standard approach in a language with sum types (variants) to build a reflection-like interface (or interpreter implementation or whatever) is to define the "any type" value like

variant value {
  I32(i32),
  I64(i64),
  // ...
}

This works great for primitive types. But now let's introduce values that can refer to more heavyweight things, such as GC object references; these are likely resources, because we need to represent ownership of the GC root.

variant value {
  I32(i32),
  I64(i64),
  GCRef(gc-ref),
  // ...
}

resource gc-ref {
  get-field: func(index: u32) -> value;
  // ...
  clone: func() -> gc-ref; // build `Clone` on top of the resource's linear type.
}

Now consider an API that takes these values, perhaps to make a function call (in the example WIT above, see inject-call):

resource funcref {
  call: func(args: list<value>);
}

This seems like a reasonable-enough model until one realizes: a variant containing any resources is linear-typed (cannot be cloned). So one is left with a few different options to make a usable API:

  • Define a "clone" ad-hoc function for value;
  • Define a "clone" helper in userspace, destructure the variant, call the ad-hoc "clone" for each resource-owning arm;
  • Avoid use of variants and build ad-hoc resources with kind: func() -> kind and unwrap_i32 / unwrap_i64 / ... methods;
  • Somehow "borrow" the values for the duration of the call.

The last option is the natural approach in languages with linear types, like Rust: in Rust, I can define

enum Value {
  I32(i32),
  I64(i64),
  GCRef(Arc<ObjectRoot>), // or whatever
  // ...
}

and pass in &Value to any function.

Unfortunately, in the resource model in the CM, borrowing is intrinsically tied to resources only, and a variant containing a resource cannot be borrowed. Because of this, in the WIT above, the true linear-typed approach ("clone everywhere") became way way too awkward, and because borrowing is not an option, I had to avoid variants entirely. This is awkward from an ergonomics PoV, and is also not great performance-wise: it means that I have a bunch of hostcalls to construct resources that represent (for example) "the constant value I32(42)", and I get to use them exactly once.

Is there a path by which we can consider supporting borrows of variants?

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