wit-bindgen icon indicating copy to clipboard operation
wit-bindgen copied to clipboard

"associated functions" and "associated methods" on non-resource types

Open tigregalis opened this issue 1 year ago • 1 comments

Given the following wit:

record color {
  r: float32,
  g: float32,
  b: float32,
  a: float32
}

record vec4 {
  x: float32,
  y: float32,
  z: float32,
  w: float32
}

// (pseudocode) associated method for color 
color.from-rgba: func(r: u8, g: u8, b: u8, a: u8) -> color; 
// (pseudocode) associated function 
color.to-vec: func(self: color) -> vec4;

when Rust bindings are generated, I'd like to allow a Rust guest to write:

let foo = Color { r: 0.1, g: 0.2, b: 0.3, a: 1.0 }; // can construct the type by struct literal syntax
let r = foo.r; // can access a field
let bar = Color::from_rgba(180, 180, 180, 255); // can construct the type with an associated function
let vec: Vec4 = bar.to_vec(); // can call an associated method

The closest I could find to this in the wit IDL document is a resource.

As syntactic sugar, resource statements can also declare any number of methods, which are functions that implicitly take a self parameter that is a handle. A resource statement can also contain any number of static functions, which do not have an implicit self parameter but are meant to be lexically nested in the scope of the resource type. Lastly, a resource statement can contain at most one constructor function, which is syntactic sugar for a function returning a handle of the containing resource type.

For example, the following resource definition:

resource blob {
    constructor(init: list<u8>);
    write: func(bytes: list<u8>);
    read: func(n: u32) -> list<u8>;
    merge: static func(lhs: borrow<blob>, rhs: borrow<blob>) -> blob;
}

desugars into:

resource blob;
%[constructor]blob: func(self: borrow<blob>, bytes: list<u8>) -> blob;
%[method]blob.write: func(self: borrow<blob>, bytes: list<u8>);
%[method]blob.read: func(self: borrow<blob>, n: u32) -> list<u8>;
%[static]blob.merge: func(lhs: borrow<blob>, rhs: borrow<blob>) -> blob;

These %-prefixed names embed the resource type name so that bindings generators can generate idiomatic syntax for the target language or (for languages like C) fall back to an appropriately-prefixed free function name.

The wit IDL document refers to these as syntactic sugar, and references a "desugaring" of sub-definitions within a resource.

This seems to imply (if it isn't purely illustrative) that %[method]blob.write: func(self: borrow<blob>, bytes: list<u8>); should be valid wit.

And in the component model document, a plainname like this is valid:

exportname    ::= <plainname>
                | <interfacename>
importname    ::= <exportname>
                | <depname>
                | <urlname>
                | <hashname>
plainname     ::= <label>
                | '[constructor]' <label>
                | '[method]' <label> '.' <label>
                | '[static]' <label> '.' <label>
label         ::= <word>
                | <label> '-' <word>
word          ::= [a-z] [0-9a-z]*
                | [A-Z] [0-9A-Z]*
...

The obvious attempt was to do:

%[constructor]color: func(r: float32, g: float32, b: float32, a: float32) -> color;
%[static]color.from-rgba: func(r: u8, g: u8, b: u8, a: u8) -> color;
%[static]color.from-hex: func(hex: u32) -> color;
%[method]color.to-vec: func(self: borrow<color>) -> vec4;
%[static]color.from-vec: func(vec: vec4) -> vec4;

However, this fails like so:

       Caused by:
           identifiers must have characters between '-'s
                --> C:\Users\...\host.wit:43:3
                 |
              43 |   %[constructor]color: func(r: float32, g: float32, b: float32, a: float32) -> color;
                 |   ^

So there seems no wit-bindgen-compatible way to express a plainname of '[constructor]' <label> | '[method]' <label> '.' <label> | '[static]' <label> '.' <label> and/or no wit-bindgen-compatible way to express "associated functions" and "associated methods" on non-resource types.

Is this something that is supported by wit? Is this something supported by the component model itself?

tigregalis avatar Jan 14 '24 13:01 tigregalis

Ah yes the "desugars into" text there is a bit handwavy and as you've discovered it's not intended that the literal desugaring works. The method naming here with [constructor] and such actually plays into validation of the component itself, and depends on resources being "nominal" types, so it wouldn't be easy to allow such constructs for arbitrary types like records and such.

There's probably two different ways this issue could be tackled, however:

  1. Add "fanciness" to the wit-bindgen Rust generator (and possibly others) to generate methods where free functions would otherwise be generated. This is just an idea, I've no idea what this would concretely look like.
  2. Propose upstream in the component-model repository that [method] and friends be allowed for more than just resources but additionally records and other types.

alexcrichton avatar Jan 17 '24 15:01 alexcrichton