Skip to content

Possible confusion between the unit type and the 0-tuple #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
alexcrichton opened this issue Apr 12, 2022 · 6 comments
Closed

Possible confusion between the unit type and the 0-tuple #21

alexcrichton opened this issue Apr 12, 2022 · 6 comments

Comments

@alexcrichton
Copy link
Collaborator

Currently interface types has both a unit as well as a tuple type, and the tuple type can have any number of fields. This raises a possible ambiguity for languages like Rust where the 0-tuple, (), is equivalent to Rust's "unit" type, also (). This might be a good opportunity to perhaps be a bit conservative and go ahead and make the 0-tuple a validation error perhaps?

Additionally one other possible thing we may want to do is in addition to making 0-tuples a validation error we could also make 1-tuples a validation error as well. At least in Rust 1-tuples are convenient for macro-generated code and things like that but otherwise are pretty rarely used. 1-tuples are pretty hard to work with as they're syntactically (T,) (not to be confused with the (T) type which is just T). Given that the component model is probably pretty far from macro-generated things it might be good to go ahead and say that 0- and 1-tuples are validation errors? (with the possibility of opening them up in the future of course)

@fitzgen
Copy link
Collaborator

fitzgen commented Apr 12, 2022

Is there a reason unit and the zero tuple are not the same type in the component model? That seems more natural to me than making a zero tuple a validation error. That is, why have a separate unit type, rather than using zero tuples for that purpose?

@lukewagner
Copy link
Member

I think it's fine if a language bindings generator maps values of two distinct interface types (unit and (tuple)) to the same language-value. I expect this will also happen in some languages where tuples and lists are both mapped to the language's indexed-array-like type. Another example would be char and (length-1) strings in some languages. That's for the interface-value-to-language-value direction; for the opposite direction: because we have a declared type signature, there's also not an ambiguity since we can just consume the given language value according to the target type. But maybe I'm missing an angle here?

(On a side note, #23 makes unit a specialization, as it probably should have been in the first place. Specializations are still distinct types (that can have distinct ABIs and language-level bindings), so it doesn't quite address what you're saying here though.)

@alexcrichton
Copy link
Collaborator Author

The original motivation for this issue is that Wasmtime, for core wasm, supports something like:

let func: wasmtime::Func = ...;
let typed_func = func.typed::<(i32, i32), (f32, f64), _>(&store)?;

where here typed_func is statically understood to take two i32 parameters and returns a f32 and f64 tuple.

For the component model I was hoping to implement something similar such as:

let func: wasmtime::component::Func = ...;
let typed_func = func.typed::<(String, u8), Vec<String>, _>(&store)?;

With this sort of type-based type assertions it's ambiguous when in Rust it's typed:

let func: wasmtime::component::Func = ...;
let typed_func = func.typed::<(Vec<()>,), (), _>(&store)?;

Is the first parameter list<unit> or list<tuple<>>? Similarly for the return value it's not certain.

It could be that the answer is simply "well that API isn't supported in the component model".

@lukewagner
Copy link
Member

Ah, right. So, if:

  • unit was defined as a specialization of (tuple) (which itself is already defined as a specialization of (record ...))
  • subtyping and type equivalence were defined to first despecialize before testing subtyping/equivalence rules
  • the Rust-type-to-Interface-Type inference considered () (in your code examples above) to have type unit

would things work out?

@alexcrichton
Copy link
Collaborator Author

I haven't really thought much about subtyping and such in the Rust bindings just yet. I was assuming that despecialize was independent of subtyping, but that may be wrong. I think overall I don't have a good enough grasp on subtyping, how it's going to work, and how it's expected to be used to judge whether making unit a specialization would be feasible or not.

One possible idea is that in Rust when we say "is the Rust type () equivalent to the interface type T" we could say "yes" for both T = unit and T = (tuple). That makes it a bit more non-idiomatic if you specifically want to only match functions that return unit instead of also those returning tuple (or something like that), though.

@lukewagner
Copy link
Member

Obviated by #69, which removed unit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants