Skip to content

Provide values for enums/constants #365

Open

Description

@vlhomutov

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.

Activity

  1. lukewagner commented on Jun 10, 2024

    @lukewagner
    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.

  2. rossberg commented on Jun 10, 2024

    @rossberg
    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.

  3. lukewagner commented on Jun 10, 2024

    @lukewagner
    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.

  4. vlhomutov commented on Jun 10, 2024

    @vlhomutov
    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.

  5. lukewagner commented on Jun 10, 2024

    @lukewagner
    Member

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

  6. vlhomutov commented on Jun 11, 2024

    @vlhomutov
    Author

    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.

  7. lukewagner commented on Jun 11, 2024

    @lukewagner
    Member

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

  8. cpetig commented on Feb 4, 2025

    @cpetig

    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?

  9. oovm commented on Feb 5, 2025

    @oovm

    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.

  10. oovm commented on Feb 5, 2025

    @oovm

    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.

  11. cpetig commented on Feb 5, 2025

    @cpetig

    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.

  12. oovm commented on Feb 6, 2025

    @oovm

    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.

  13. cpetig commented on Feb 6, 2025

    @cpetig

    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.

  14. cpetig commented on Feb 6, 2025

    @cpetig

    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?

  15. lukewagner commented on Feb 6, 2025

    @lukewagner
    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

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