Skip to content
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

Provide values for enums/constants #365

Open
vlhomutov opened this issue Jun 5, 2024 · 15 comments
Open

Provide values for enums/constants #365

vlhomutov opened this issue Jun 5, 2024 · 15 comments

Comments

@vlhomutov
Copy link

When using WIT to describe some existing interface, it is sometimes required
to have enums with specific values. But there is no way to express this in WIT
currently.

Think of bitflags,for example:

we have something simple like this (C)

#define FOO  0x2
#define BAR 0x4

int  some_func(int arg) {
   if (arg == 1)  return FOO; else return BAR;
}

Or your function returns NEGATIVE constants (ERR_X = -1, ERR_Y =-33, ERR_Z = -22)

Or maybe your interface has some magic constants like MY_BETTER_PI=3.44 that needs to be passed around
or may be returned from functions.

I understand that WIT is about TYPES, but plain types are just not enough to describe an interface.
Because currently WIT definition is the only point where your host application can provide this data
to unknown number of its clients in unknown to the host languages.
Otherwise, we are back to days when we had to parse C includes to be able to use FFI.

@lukewagner
Copy link
Member

Hi, this is a good question so thanks for filing an issue and sorry for the slow reply (last week was the in-person CG meeting)!

So coincidentally, with the recently-merged #336, I think we have a Component Model primitive that matches what you're describing. In particular, when defining an interface, in WAT I can write:

(type (instance
  (value $foo u32 2)
  (value $bar u32 4)
  (export "foo" (value (eq $foo)))
  (export "bar" (value (eq $bar)))
  (export "some_func" (func (param "arg" u32) (result u32)))
))

#336 didn't add WIT-level syntax to capture this new functionality, but we should before too long, and so you could imagine that in WIT you can write something like:

interface whatever {
  foo = 2;
  bar = 4;
  some_func: func(arg: u32) -> u32;
}

Now, an obvious improvement that you might want is a more direct typed connection between foo/bar and the return-type of some_func, which might be nice for bindings generators to, e.g., scope the constants. Now of course you can get this with an enum:

interface whatever {
  enum e { foo, bar };
  some_func: func(arg: u32) -> e;
}

and a C code generator can generate #defines for foo and bar so you can get the raw integer values. But now you've lost the ability to control the specific integer values. However, if we were to allow enums to declare arbitrary integral values, that opens up a tricky question: does the CABI guarantee that only valid enum values are passed? If "no", than it's not really an enum, it's really just some integer type with any allowable integer value. If "yes", then the CABI has to do some possibly-expensive validation logic and also there are legitimate reasons for an interface author to intentionally allow more values than just those explicitly declared. While I can imagine a new type that works like an integer with associated constant values, I wonder if it's worth it when it seems like the above value definitions (which are scoped, btw, by the interface) is Good Enough, at least for the time being.

@rossberg
Copy link
Member

I would also argue that integer mappings of enum values are not an interface concern but one of lowering to a specific language, and only for those languages that actually expose enums as ints in the first place.

@lukewagner
Copy link
Member

Yeah, I think that's why we should consider this entirely separate from enums; if there is a new feature to consider adding here, it's some sort of specialization of an integer with a named subset of values.

@vlhomutov
Copy link
Author

I agree that if you set values in enums, it is not a enum longer and they should not be extended this way.
Still we need some way to set named values in the interface description, along with the type. How this will
be translated to target language is a task for the specific generator. Obvious candidates are named constants.
People may want to define versions, dates, cryptographic values, and other various numbers that are required to
use the declared interface.
Not sure though how far this can go - we start with numeric constants and at some day we may end up with arrays,
records and objects with constructors. Maybe this should be different language that describes data.

@lukewagner
Copy link
Member

Did the middle code snippet in my first comment make sense, or were you imagining something else?

@vlhomutov
Copy link
Author

vlhomutov commented Jun 11, 2024

Did the middle code snippet in my first comment make sense, or were you imagining something else?

Yes, absolutely. Maybe even "u32 bar: 42", so we have control over types sizes.

@lukewagner
Copy link
Member

Oops, yes, you're right; the type would be necessary as part of the value definition in the WIT too.

@cpetig
Copy link

cpetig commented Feb 4, 2025

As this came up today as one of the helpful features for interfacing with existing C/++ code, I would like to bring up the question of the intended WIT syntax again.

Do I guess correctly that foo: u32 = 2; would be the right syntax for this?

@oovm
Copy link

oovm commented Feb 5, 2025

This feature is also needed when implementing some protocols.

It is not up to me to decide what the enumeration value is, but to follow the existing protocol.


Of course, some conventions can be added:

1.If an enumeration has a value, then all enumerations must have values.
2.No enumeration value means that the enumeration value is not important, in principle, it can be assigned 0..n.

@oovm
Copy link

oovm commented Feb 5, 2025

As for constant values, I think it is not necessary after having static getter.

The static getter system can also add additional properties such as lazy, once, and thread_local.

Overall, the functionality and extensibility are higher than the constant system.

@cpetig
Copy link

cpetig commented Feb 5, 2025

This feature is also needed when implementing some protocols.

It is not up to me to decide what the enumeration value is, but to follow the existing protocol.

Of course, some conventions can be added:

1.If an enumeration has a value, then all enumerations must have values. 2.No enumeration value means that the enumeration value is not important, in principle, it can be assigned 0..n.

I try to find a good mapping for C style enums which assign the numbers in a non-contiguous way, often seen in protocol specifications. Luke's argument that in the runtime you might not be able to quickly check whether a value is valid convinced me that indeed an enum is not the right WIT equivalent but some integer.

So likely

type port = u16;
http: port = 80;
pop3: port = 110;
connect: func(port: port) -> result<connection>;

is a good fit for this mechanism.

As for constant values, I think it is not necessary after having #235.

I feel that both are equally necessary, and originate from different binding languages: These enum like types are needed for C/C++/Rust bindings which specify the value/discriminant and the getter abstraction benefits Java(Script)/C# and similar bindings.

@oovm
Copy link

oovm commented Feb 6, 2025

Can we split enums and constants into two issues, discuss them separately, and set up feature-gates separately?

I realize that they are very different, and we should focus on constants here.

@cpetig
Copy link

cpetig commented Feb 6, 2025

Can we split enums and constants into two issues, discuss them separately, and set up feature-gates separately?

I realize that they are very different, and we should focus on constants here.

I am confused, I think I proposed a constant syntax for WIT and found that they nicely solve my immediate needs for enum values.

Discussing constant syntax here and splitting off enums would recommend to rename this issue to "constant syntax in WIT" and open a new issue with this title, right?

PS: I will probably take a try at implementing above syntax in wasm-tools and wit-bindgen.

@cpetig
Copy link

cpetig commented Feb 6, 2025

PPS: I now got it, I never really realized that the title mentions both types, do you feel that this solution isn't suitable for both?

@lukewagner
Copy link
Member

@cpetig

Do I guess correctly that foo: u32 = 2; would be the right syntax for this?

That looks good to me! Feel free to create a PR extending the grammar in WIT.md or, if you get something working in wit-bindgen first, let know and I can create the spec PR.

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

5 participants