[IDEA] Implied enum types

Aaron called this "context-dependence" in his classic post on the Reasoning Footprint.

The example about type annotations describes it thusly:

Context-dependence: because data types and functions are annotated, it's easy to determine the information that's influencing the outcome of inference. You only need to look shallowly at code outside of the current function. Another way of saying this is that type inference is performed modularly, one function body at a time.

I would say that "look shallowly" is a good description of what's needed for implied enum types. After all, even set_radio_state(RadioState::Enabled)? isn't just local, because RadioState itself would often come from a use, forcing you to look elsewhere in the file.

Looking at the other two axes:

  • Applicability: I use the set_radio_state(.Enabled)? syntax in my posts here to give it that "heads-up that this [is] happening". And it's only elidable where inference has already determined what the type must be -- dbg!(.Enabled) would never work.

  • Power: Because it only works where the type is already forced by something else, this can't "radically change program behaviour or its types".

So yes, this does have some impact on locality of reasoning, but not a particularly bad one -- far less than trait method resolution, for example. And it's low impact on the other axes, so I see it as fitting well in the kinds of tradeoffs that Rust has generally been happy to make.

I think, instead, that this is the core issue:

My personal concern with this, as I linked above, is entirely about naming:

The problem is not in locality of reference, but in naming conventions.

Calling foo(.Yes, .Yes) is as bad as foo(true, true), but people make enums like that today because this feature doesn't exist (see InheritStability::Yes and InheritDeprecation::Yes, for example), and thus the call would be foo(InheritStability::Yes, InheritDeprecation::Yes), which is entirely readable.

Thus to me, if Rust was designed around this feature it wouldn't be .Disable, but .DisableRadio, and you'd solve your update problem by removing the Disable variant and calling it DisableWifi or DisableAllRadios, so you still get the compiler error for the callsites, just in a different way.

Therefore I think the big unknown here is in how to get the goodness of not needing to say the irrelevant when it's irrelevant, but still make sense with the general non-stuttering scoped nature of enums in existing Rust. The non-localness, by itself, is not the issue.

(And, of course, there's the big overarching question of whether the language grammar needs to ensure this, or whether -- like foo(true, true) -- the appropriate mitigation might just be code review.)

I consider this a false parallel, because the potential impacts are so different. UB makes it impossible to reason about your program, because a program that hits UB is allowed to do literally anything. Getting a variant from the only enum that can possibly be passed to a function is of tiny impact, relatively.

Logic errors are always possible, but when it's just a logic problem and not UB, then logging and debuggers and testing and such are meaningful.

4 Likes