Importing associated constants?


#1

I have a code generator which is using structs with associated constants as an open-ended enum. For example:

struct Thing(pub u32);

impl Thing {
    const FOO: u32 = 0;
    const BAR: u32 = 1;
    //...
}

However, unlike an enum, I can’t do use Thing::* to allow direct use of FOO/BAR/etc (or even an explicit use Thing::{FOO, BAR}). Is there some fundamental reason to not allow this, or is there already an RFC?


#2

It sounds like what you really want is a #[non_exhaustive] enum, though that’s still unstable: https://github.com/rust-lang/rust/issues/44109


#3

Hi! Thanks for the suggestion, but Rust enums don’t do the trick for a couple of reasons:

  • it can’t handle duplicate values
  • it can’t handle unknown values

Basically, this is to handle a wire-protocol which has C-like enum semantics overlayed on a plain u32. I tried to model this with Rust enums by added an Unknown(u32) field, but the u32<->enum conversions are cumbersome, and it still can’t handle duplicate values.

As I result, I arrived at using associated constants on a newtype, which meets the needs well - aside from the inconvenience of not being able to import all the constants. I was considering making a module with the same name that also includes the constants, but I haven’t worked out how well that would work.


#4

Things could get a bit weird if your type is parameterized.


#5

I think we may be able to support importing associated items using type-based resolution (like Thing::FOO from the example) with some limitations.

Consider this import:

use Prefix::AssocItem;

Here Prefix is a type and AssocItem is something that potentially may be an associated item, but we can’t know that for sure without doing type checking, trait resolution, etc, because Prefix may be a type alias that can include everything inside it including associated types.
On another hand, we cannot delay import resolution until type checking, it will raise compiler complexity enormously.

What we can do is to reserve slots named AssocItem in all namespaces of the current module, (or at least type and value namespaces because associated items can only live there, but maybe the macro namespace as well, just in case) and create some kind of virtual items.
After that import resolution and other name resolution can work as usual possibly resolving paths to definitions of those virtual items.
Note that these greedily reserved imports may create more conflicts with other items than normal imports and will shadow glob imports (these are “some limitations” mentioned above).

When type checking is done we have all the necessary information and can figure out true definitions of these virtual items (possibly reporting errors if no such definition exists or it’s ambiguous).

Regarding generic arguments, I think we can consider them “not provided”.
So use of AssocItem may be a “can’t infer type parameters” error, or it may be not if the parameters can be inferred or defaulted. The alternative is the “generic argument transfer” hack employed for enum variants (Enum::Variant<T> actually means Enum<T>::Variant), but that would be bad because associated functions and soon types/constants may have their own type parameters unlike variants.